2 * Copyright (c) 2010-2024 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.Objects;
20 import java.util.Properties;
22 import java.util.regex.Matcher;
23 import java.util.regex.Pattern;
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.openhab.persistence.jdbc.internal.db.JdbcBaseDAO;
28 import org.openhab.persistence.jdbc.internal.utils.MovingAverage;
29 import org.openhab.persistence.jdbc.internal.utils.StringUtilsExt;
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
36 * @author Helmut Lehmeyer - Initial contribution
39 public class JdbcConfiguration {
40 private final Logger logger = LoggerFactory.getLogger(JdbcConfiguration.class);
42 private static final Pattern EXTRACT_CONFIG_PATTERN = Pattern.compile("^(.*?)\\.([0-9.a-zA-Z]+)$");
43 private static final String DB_DAO_PACKAGE = "org.openhab.persistence.jdbc.internal.db.Jdbc";
45 private Map<Object, Object> configuration;
47 private @NonNullByDefault({}) JdbcBaseDAO dBDAO;
48 private @Nullable String dbName;
49 boolean dbConnected = false;
50 boolean driverAvailable = false;
52 private @Nullable String serviceName;
53 private String name = "jdbc";
54 public final boolean valid;
56 // private String url;
57 // private String user;
58 // private String password;
59 private int numberDecimalcount = 3;
60 private boolean tableUseRealItemNames = false;
61 private boolean tableCaseSensitiveItemNames = false;
62 private String itemsManageTable = "items";
63 private String tableNamePrefix = "item";
64 private int tableIdDigitCount = 4;
65 private boolean rebuildTableNames = false;
67 private int errReconnectThreshold = 0;
69 public int timerCount = 0;
70 public int time1000Statements = 0;
71 public long timer1000 = 0;
72 public MovingAverage timeAverage50arr = new MovingAverage(50);
73 public MovingAverage timeAverage100arr = new MovingAverage(100);
74 public MovingAverage timeAverage200arr = new MovingAverage(200);
75 public boolean enableLogTime = false;
77 public JdbcConfiguration(Map<Object, Object> configuration) {
78 logger.debug("JDBC::JdbcConfiguration");
79 this.configuration = configuration;
80 valid = updateConfig();
83 private boolean updateConfig() {
84 logger.debug("JDBC::updateConfig: configuration size = {}", configuration.size());
86 String user = (String) configuration.get("user");
87 String password = (String) configuration.get("password");
90 String url = (String) configuration.get("url");
93 logger.error("Mandatory url parameter is missing in configuration!");
97 Properties parsedURL = StringUtilsExt.parseJdbcURL(url);
99 if (user == null || user.isBlank()) {
100 logger.debug("No jdbc:user parameter defined in jdbc.cfg");
102 if (password == null || password.isBlank()) {
103 logger.debug("No jdbc:password parameter defined in jdbc.cfg.");
108 "JDBC url is missing - please configure in jdbc.cfg like 'jdbc:<service>:<host>[:<port>;<attributes>]'");
112 if ("false".equalsIgnoreCase(parsedURL.getProperty("parseValid"))) {
113 Enumeration<?> en = parsedURL.propertyNames();
115 for (Object key : Collections.list(en)) {
116 enstr += key + " = " + parsedURL.getProperty("" + key) + "\n";
119 "JDBC url is not well formatted: {}\nPlease configure in openhab.cfg like 'jdbc:<service>:<host>[:<port>;<attributes>]'",
124 logger.debug("JDBC::updateConfig: user={}", user);
125 logger.debug("JDBC::updateConfig: password exists? {}", password != null && !password.isBlank());
126 logger.debug("JDBC::updateConfig: url={}", url);
128 // set database type and database type class
129 setDBDAOClass(Objects.requireNonNull(parsedURL.getProperty("dbShortcut"))); // derby, h2, hsqldb, mariadb,
130 // mysql, postgresql,
131 // sqlite, timescaledb
133 if (user != null && !user.isBlank()) {
134 dBDAO.databaseProps.setProperty("dataSource.user", user);
138 if (password != null && !password.isBlank()) {
139 dBDAO.databaseProps.setProperty("dataSource.password", password);
142 // set sql-types from external config
145 final Pattern isNumericPattern = Pattern.compile("\\d+(\\.\\d+)?");
146 String et = (String) configuration.get("reconnectCnt");
147 if (et != null && !et.isBlank() && isNumericPattern.matcher(et).matches()) {
148 errReconnectThreshold = Integer.parseInt(et);
149 logger.debug("JDBC::updateConfig: errReconnectThreshold={}", errReconnectThreshold);
152 String mt = (String) configuration.get("itemsManageTable");
153 if (mt != null && !mt.isBlank()) {
154 itemsManageTable = mt;
155 logger.debug("JDBC::updateConfig: itemsManageTable={}", itemsManageTable);
158 String np = (String) configuration.get("tableNamePrefix");
159 if (np != null && !np.isBlank()) {
160 tableNamePrefix = np;
161 logger.debug("JDBC::updateConfig: tableNamePrefix={}", tableNamePrefix);
164 String dd = (String) configuration.get("numberDecimalcount");
165 if (dd != null && !dd.isBlank() && isNumericPattern.matcher(dd).matches()) {
166 numberDecimalcount = Integer.parseInt(dd);
167 logger.debug("JDBC::updateConfig: numberDecimalcount={}", numberDecimalcount);
170 String rn = (String) configuration.get("tableUseRealItemNames");
171 if (rn != null && !rn.isBlank()) {
172 tableUseRealItemNames = "true".equals(rn) ? Boolean.parseBoolean(rn) : false;
173 logger.debug("JDBC::updateConfig: tableUseRealItemNames={}", tableUseRealItemNames);
176 String lc = (String) configuration.get("tableCaseSensitiveItemNames");
177 if (lc != null && !lc.isBlank()) {
178 tableCaseSensitiveItemNames = Boolean.parseBoolean(lc);
179 logger.debug("JDBC::updateConfig: tableCaseSensitiveItemNames={}", tableCaseSensitiveItemNames);
182 String td = (String) configuration.get("tableIdDigitCount");
183 if (td != null && !td.isBlank() && isNumericPattern.matcher(td).matches()) {
184 tableIdDigitCount = Integer.parseInt(td);
185 logger.debug("JDBC::updateConfig: tableIdDigitCount={}", tableIdDigitCount);
188 String rt = (String) configuration.get("rebuildTableNames");
189 if (rt != null && !rt.isBlank()) {
190 rebuildTableNames = Boolean.parseBoolean(rt);
191 logger.debug("JDBC::updateConfig: rebuildTableNames={}", rebuildTableNames);
195 String ac = (String) configuration.get("maximumPoolSize");
196 if (ac != null && !ac.isBlank()) {
197 dBDAO.databaseProps.setProperty("maximumPoolSize", ac);
201 String ic = (String) configuration.get("minimumIdle");
202 if (ic != null && !ic.isBlank()) {
203 dBDAO.databaseProps.setProperty("minimumIdle", ic);
207 String it = (String) configuration.get("idleTimeout");
208 if (it != null && !it.isBlank()) {
209 dBDAO.databaseProps.setProperty("idleTimeout", it);
212 String ent = (String) configuration.get("enableLogTime");
213 if (ent != null && !ent.isBlank()) {
214 enableLogTime = "true".equals(ent) ? Boolean.parseBoolean(ent) : false;
216 logger.debug("JDBC::updateConfig: enableLogTime {}", enableLogTime);
219 String fd = (String) configuration.get("driverClassName");
220 if (fd != null && !fd.isBlank()) {
221 dBDAO.databaseProps.setProperty("driverClassName", fd);
225 String ds = (String) configuration.get("dataSourceClassName");
226 if (ds != null && !ds.isBlank()) {
227 dBDAO.databaseProps.setProperty("dataSourceClassName", ds);
231 String dn = dBDAO.databaseProps.getProperty("driverClassName");
233 dn = dBDAO.databaseProps.getProperty("dataSourceClassName");
235 dBDAO.databaseProps.setProperty("jdbcUrl", url);
238 // test if JDBC driver bundle is available
239 testJDBCDriver(Objects.requireNonNull(dn));
241 logger.debug("JDBC::updateConfig: configuration complete. service={}", getName());
246 private void setDBDAOClass(String sn) {
250 if (sn.isBlank() || sn.length() < 2) {
252 "JDBC::updateConfig: Required database url like 'jdbc:<service>:<host>[:<port>;<attributes>]' - please configure the jdbc:url parameter in openhab.cfg");
253 serviceName = "none";
257 this.serviceName = serviceName;
258 logger.debug("JDBC::updateConfig: found serviceName = '{}'", serviceName);
260 // set class for database type
261 String ddp = DB_DAO_PACKAGE + serviceName.toUpperCase().charAt(0) + serviceName.toLowerCase().substring(1)
264 logger.debug("JDBC::updateConfig: Init Data Access Object Class: '{}'", ddp);
266 dBDAO = (JdbcBaseDAO) Class.forName(ddp).getConstructor().newInstance();
267 logger.debug("JDBC::updateConfig: dBDAO ClassName={}", dBDAO.getClass().getName());
268 } catch (IllegalAccessException | InstantiationException | InvocationTargetException
269 | NoSuchMethodException e) {
270 logger.error("JDBC::updateConfig: Exception: {}", e.getMessage());
271 } catch (ClassNotFoundException e) {
272 logger.warn("JDBC::updateConfig: no Configuration for serviceName '{}' found. ClassNotFoundException: {}",
273 serviceName, e.getMessage());
274 logger.debug("JDBC::updateConfig: using default Database Configuration: JdbcBaseDAO !!");
275 dBDAO = new JdbcBaseDAO();
276 logger.debug("JDBC::updateConfig: dBConfig done");
280 private void setSqlTypes() {
281 Set<Object> keys = configuration.keySet();
283 for (Object k : keys) {
284 String key = (String) k;
285 Matcher matcher = EXTRACT_CONFIG_PATTERN.matcher(key);
286 if (!matcher.matches()) {
291 if (!"sqltype".equals(matcher.group(1))) {
294 String itemType = matcher.group(2);
295 if (!itemType.startsWith("table")) {
296 itemType = itemType.toUpperCase() + "ITEM";
298 String value = (String) configuration.get(key);
299 logger.debug("JDBC::updateConfig: set sqlTypes: itemType={} value={}", itemType, value);
301 dBDAO.sqlTypes.put(itemType, value);
306 private void testJDBCDriver(String driver) {
307 driverAvailable = true;
309 Class.forName(driver);
310 logger.debug("JDBC::updateConfig: load JDBC-driverClass was successful: '{}'", driver);
311 } catch (ClassNotFoundException e) {
312 driverAvailable = false;
314 "JDBC::updateConfig: could NOT load JDBC-driverClassName or JDBC-dataSourceClassName. ClassNotFoundException: '{}'",
317 + "\n\n\t!!!\n\tTo avoid this error, place an appropriate JDBC driver file for serviceName '{}' in addons directory.\n"
318 + "\tCopy missing JDBC-Driver-jar to your openHab/addons Folder.\n\t!!!\n" + "\tDOWNLOAD: \n";
319 String serviceName = this.serviceName;
320 if (serviceName != null) {
321 switch (serviceName) {
323 warn += "\tDerby: version >= 10.14.2.0 from https://mvnrepository.com/artifact/org.apache.derby/derby\n";
326 warn += "\tH2: version >= 2.2.224 from https://mvnrepository.com/artifact/com.h2database/h2\n";
329 warn += "\tHSQLDB: version >= 2.3.3 from https://mvnrepository.com/artifact/org.hsqldb/hsqldb\n";
332 warn += "\tMariaDB: version >= 3.0.8 from https://mvnrepository.com/artifact/org.mariadb.jdbc/mariadb-java-client\n";
335 warn += "\tMySQL: version >= 8.2.0 from https://mvnrepository.com/artifact/com.mysql/mysql-connector-j\n";
338 warn += "\tPostgreSQL:version >= 42.4.4 from https://mvnrepository.com/artifact/org.postgresql/postgresql\n";
341 warn += "\tSQLite: version >= 3.42.0.0 from https://mvnrepository.com/artifact/org.xerial/sqlite-jdbc\n";
345 logger.warn(warn, serviceName);
349 public Properties getHikariConfiguration() {
350 return dBDAO.getConnectionProperties();
353 public String getName() {
354 // return serviceName;
358 public @Nullable String getServiceName() {
362 public String getItemsManageTable() {
363 return itemsManageTable;
366 public String getTableNamePrefix() {
367 return tableNamePrefix;
370 public int getErrReconnectThreshold() {
371 return errReconnectThreshold;
374 public boolean getRebuildTableNames() {
375 return rebuildTableNames;
378 public int getNumberDecimalcount() {
379 return numberDecimalcount;
382 public boolean getTableUseRealItemNames() {
383 return tableUseRealItemNames;
386 public boolean getTableCaseSensitiveItemNames() {
387 return tableCaseSensitiveItemNames;
391 * Checks if real item names (without number suffix) is enabled.
393 * @return true if both tableUseRealItemNames and tableCaseSensitiveItemNames are enabled.
395 public boolean getTableUseRealCaseSensitiveItemNames() {
396 return tableUseRealItemNames && tableCaseSensitiveItemNames;
399 public int getTableIdDigitCount() {
400 return tableIdDigitCount;
403 public JdbcBaseDAO getDBDAO() {
407 public @Nullable String getDbName() {
411 public void setDbName(String dbName) {
412 this.dbName = dbName;
415 public boolean isDbConnected() {
419 public void setDbConnected(boolean dbConnected) {
420 logger.debug("JDBC::setDbConnected {}", dbConnected);
421 // Initializing step, after db is connected.
422 // Initialize sqlTypes, depending on DB version for example
423 dBDAO.initAfterFirstDbConnection();
424 // Running once again to prior external configured SqlTypes!
426 this.dbConnected = dbConnected;
429 public boolean isDriverAvailable() {
430 return driverAvailable;