]> git.basschouten.com Git - openhab-addons.git/blob
46cf6343f062f8b2410647e7c660eeab84f59fa5
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.persistence.jdbc.internal;
14
15 import java.lang.reflect.InvocationTargetException;
16 import java.util.Collections;
17 import java.util.Enumeration;
18 import java.util.Map;
19 import java.util.Properties;
20 import java.util.Set;
21 import java.util.regex.Matcher;
22 import java.util.regex.Pattern;
23
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.openhab.persistence.jdbc.internal.db.JdbcBaseDAO;
27 import org.openhab.persistence.jdbc.internal.utils.MovingAverage;
28 import org.openhab.persistence.jdbc.internal.utils.StringUtilsExt;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31
32 /**
33  * Configuration class
34  *
35  * @author Helmut Lehmeyer - Initial contribution
36  */
37 @NonNullByDefault
38 public class JdbcConfiguration {
39     private final Logger logger = LoggerFactory.getLogger(JdbcConfiguration.class);
40
41     private static final Pattern EXTRACT_CONFIG_PATTERN = Pattern.compile("^(.*?)\\.([0-9.a-zA-Z]+)$");
42     private static final String DB_DAO_PACKAGE = "org.openhab.persistence.jdbc.internal.db.Jdbc";
43
44     private Map<Object, Object> configuration;
45
46     private @NonNullByDefault({}) JdbcBaseDAO dBDAO;
47     private @Nullable String dbName;
48     boolean dbConnected = false;
49     boolean driverAvailable = false;
50
51     private @Nullable String serviceName;
52     private String name = "jdbc";
53     public final boolean valid;
54
55     // private String url;
56     // private String user;
57     // private String password;
58     private int numberDecimalcount = 3;
59     private boolean tableUseRealItemNames = false;
60     private boolean tableCaseSensitiveItemNames = false;
61     private String itemsManageTable = "items";
62     private String tableNamePrefix = "item";
63     private int tableIdDigitCount = 4;
64     private boolean rebuildTableNames = false;
65
66     private int errReconnectThreshold = 0;
67
68     public int timerCount = 0;
69     public int time1000Statements = 0;
70     public long timer1000 = 0;
71     public MovingAverage timeAverage50arr = new MovingAverage(50);
72     public MovingAverage timeAverage100arr = new MovingAverage(100);
73     public MovingAverage timeAverage200arr = new MovingAverage(200);
74     public boolean enableLogTime = false;
75
76     public JdbcConfiguration(Map<Object, Object> configuration) {
77         logger.debug("JDBC::JdbcConfiguration");
78         this.configuration = configuration;
79         valid = updateConfig();
80     }
81
82     private boolean updateConfig() {
83         logger.debug("JDBC::updateConfig: configuration size = {}", configuration.size());
84
85         String user = (String) configuration.get("user");
86         String password = (String) configuration.get("password");
87
88         // mandatory url
89         String url = (String) configuration.get("url");
90
91         if (url == null) {
92             logger.error("Mandatory url parameter is missing in configuration!");
93             return false;
94         }
95
96         Properties parsedURL = StringUtilsExt.parseJdbcURL(url);
97
98         if (user == null || user.isBlank()) {
99             logger.debug("No jdbc:user parameter defined in jdbc.cfg");
100         }
101         if (password == null || password.isBlank()) {
102             logger.debug("No jdbc:password parameter defined in jdbc.cfg.");
103         }
104
105         if (url.isBlank()) {
106             logger.debug(
107                     "JDBC url is missing - please configure in jdbc.cfg like 'jdbc:<service>:<host>[:<port>;<attributes>]'");
108             return false;
109         }
110
111         if ("false".equalsIgnoreCase(parsedURL.getProperty("parseValid"))) {
112             Enumeration<?> en = parsedURL.propertyNames();
113             String enstr = "";
114             for (Object key : Collections.list(en)) {
115                 enstr += key + " = " + parsedURL.getProperty("" + key) + "\n";
116             }
117             logger.warn(
118                     "JDBC url is not well formatted: {}\nPlease configure in openhab.cfg like 'jdbc:<service>:<host>[:<port>;<attributes>]'",
119                     enstr);
120             return false;
121         }
122
123         logger.debug("JDBC::updateConfig: user={}", user);
124         logger.debug("JDBC::updateConfig: password exists? {}", password != null && !password.isBlank());
125         logger.debug("JDBC::updateConfig: url={}", url);
126
127         // set database type and database type class
128         setDBDAOClass(parsedURL.getProperty("dbShortcut")); // derby, h2, hsqldb, mariadb, mysql, postgresql,
129                                                             // sqlite, timescaledb
130         // set user
131         if (user != null && !user.isBlank()) {
132             dBDAO.databaseProps.setProperty("dataSource.user", user);
133         }
134
135         // set password
136         if (password != null && !password.isBlank()) {
137             dBDAO.databaseProps.setProperty("dataSource.password", password);
138         }
139
140         // set sql-types from external config
141         setSqlTypes();
142
143         final Pattern isNumericPattern = Pattern.compile("\\d+(\\.\\d+)?");
144         String et = (String) configuration.get("reconnectCnt");
145         if (et != null && !et.isBlank() && isNumericPattern.matcher(et).matches()) {
146             errReconnectThreshold = Integer.parseInt(et);
147             logger.debug("JDBC::updateConfig: errReconnectThreshold={}", errReconnectThreshold);
148         }
149
150         String mt = (String) configuration.get("itemsManageTable");
151         if (mt != null && !mt.isBlank()) {
152             itemsManageTable = mt;
153             logger.debug("JDBC::updateConfig: itemsManageTable={}", itemsManageTable);
154         }
155
156         String np = (String) configuration.get("tableNamePrefix");
157         if (np != null && !np.isBlank()) {
158             tableNamePrefix = np;
159             logger.debug("JDBC::updateConfig: tableNamePrefix={}", tableNamePrefix);
160         }
161
162         String dd = (String) configuration.get("numberDecimalcount");
163         if (dd != null && !dd.isBlank() && isNumericPattern.matcher(dd).matches()) {
164             numberDecimalcount = Integer.parseInt(dd);
165             logger.debug("JDBC::updateConfig: numberDecimalcount={}", numberDecimalcount);
166         }
167
168         String rn = (String) configuration.get("tableUseRealItemNames");
169         if (rn != null && !rn.isBlank()) {
170             tableUseRealItemNames = "true".equals(rn) ? Boolean.parseBoolean(rn) : false;
171             logger.debug("JDBC::updateConfig: tableUseRealItemNames={}", tableUseRealItemNames);
172         }
173
174         String lc = (String) configuration.get("tableCaseSensitiveItemNames");
175         if (lc != null && !lc.isBlank()) {
176             tableCaseSensitiveItemNames = Boolean.parseBoolean(lc);
177             logger.debug("JDBC::updateConfig: tableCaseSensitiveItemNames={}", tableCaseSensitiveItemNames);
178         }
179
180         String td = (String) configuration.get("tableIdDigitCount");
181         if (td != null && !td.isBlank() && isNumericPattern.matcher(td).matches()) {
182             tableIdDigitCount = Integer.parseInt(td);
183             logger.debug("JDBC::updateConfig: tableIdDigitCount={}", tableIdDigitCount);
184         }
185
186         String rt = (String) configuration.get("rebuildTableNames");
187         if (rt != null && !rt.isBlank()) {
188             rebuildTableNames = Boolean.parseBoolean(rt);
189             logger.debug("JDBC::updateConfig: rebuildTableNames={}", rebuildTableNames);
190         }
191
192         // undocumented
193         String ac = (String) configuration.get("maximumPoolSize");
194         if (ac != null && !ac.isBlank()) {
195             dBDAO.databaseProps.setProperty("maximumPoolSize", ac);
196         }
197
198         // undocumented
199         String ic = (String) configuration.get("minimumIdle");
200         if (ic != null && !ic.isBlank()) {
201             dBDAO.databaseProps.setProperty("minimumIdle", ic);
202         }
203
204         // undocumented
205         String it = (String) configuration.get("idleTimeout");
206         if (it != null && !it.isBlank()) {
207             dBDAO.databaseProps.setProperty("idleTimeout", it);
208         }
209         // undocumented
210         String ent = (String) configuration.get("enableLogTime");
211         if (ent != null && !ent.isBlank()) {
212             enableLogTime = "true".equals(ent) ? Boolean.parseBoolean(ent) : false;
213         }
214         logger.debug("JDBC::updateConfig: enableLogTime {}", enableLogTime);
215
216         // undocumented
217         String fd = (String) configuration.get("driverClassName");
218         if (fd != null && !fd.isBlank()) {
219             dBDAO.databaseProps.setProperty("driverClassName", fd);
220         }
221
222         // undocumented
223         String ds = (String) configuration.get("dataSourceClassName");
224         if (ds != null && !ds.isBlank()) {
225             dBDAO.databaseProps.setProperty("dataSourceClassName", ds);
226         }
227
228         // undocumented
229         String dn = dBDAO.databaseProps.getProperty("driverClassName");
230         if (dn == null) {
231             dn = dBDAO.databaseProps.getProperty("dataSourceClassName");
232         } else {
233             dBDAO.databaseProps.setProperty("jdbcUrl", url);
234         }
235
236         // test if JDBC driver bundle is available
237         testJDBCDriver(dn);
238
239         logger.debug("JDBC::updateConfig: configuration complete. service={}", getName());
240
241         return true;
242     }
243
244     private void setDBDAOClass(String sn) {
245         String serviceName;
246
247         // set database type
248         if (sn.isBlank() || sn.length() < 2) {
249             logger.error(
250                     "JDBC::updateConfig: Required database url like 'jdbc:<service>:<host>[:<port>;<attributes>]' - please configure the jdbc:url parameter in openhab.cfg");
251             serviceName = "none";
252         } else {
253             serviceName = sn;
254         }
255         this.serviceName = serviceName;
256         logger.debug("JDBC::updateConfig: found serviceName = '{}'", serviceName);
257
258         // set class for database type
259         String ddp = DB_DAO_PACKAGE + serviceName.toUpperCase().charAt(0) + serviceName.toLowerCase().substring(1)
260                 + "DAO";
261
262         logger.debug("JDBC::updateConfig: Init Data Access Object Class: '{}'", ddp);
263         try {
264             dBDAO = (JdbcBaseDAO) Class.forName(ddp).getConstructor().newInstance();
265             logger.debug("JDBC::updateConfig: dBDAO ClassName={}", dBDAO.getClass().getName());
266         } catch (IllegalAccessException | InstantiationException | InvocationTargetException
267                 | NoSuchMethodException e) {
268             logger.error("JDBC::updateConfig: Exception: {}", e.getMessage());
269         } catch (ClassNotFoundException e) {
270             logger.warn("JDBC::updateConfig: no Configuration for serviceName '{}' found. ClassNotFoundException: {}",
271                     serviceName, e.getMessage());
272             logger.debug("JDBC::updateConfig: using default Database Configuration: JdbcBaseDAO !!");
273             dBDAO = new JdbcBaseDAO();
274             logger.debug("JDBC::updateConfig: dBConfig done");
275         }
276     }
277
278     private void setSqlTypes() {
279         Set<Object> keys = configuration.keySet();
280
281         for (Object k : keys) {
282             String key = (String) k;
283             Matcher matcher = EXTRACT_CONFIG_PATTERN.matcher(key);
284             if (!matcher.matches()) {
285                 continue;
286             }
287             matcher.reset();
288             matcher.find();
289             if (!"sqltype".equals(matcher.group(1))) {
290                 continue;
291             }
292             String itemType = matcher.group(2);
293             if (!itemType.startsWith("table")) {
294                 itemType = itemType.toUpperCase() + "ITEM";
295             }
296             String value = (String) configuration.get(key);
297             logger.debug("JDBC::updateConfig: set sqlTypes: itemType={} value={}", itemType, value);
298             if (value != null) {
299                 dBDAO.sqlTypes.put(itemType, value);
300             }
301         }
302     }
303
304     private void testJDBCDriver(String driver) {
305         driverAvailable = true;
306         try {
307             Class.forName(driver);
308             logger.debug("JDBC::updateConfig: load JDBC-driverClass was successful: '{}'", driver);
309         } catch (ClassNotFoundException e) {
310             driverAvailable = false;
311             logger.error(
312                     "JDBC::updateConfig: could NOT load JDBC-driverClassName or JDBC-dataSourceClassName. ClassNotFoundException: '{}'",
313                     e.getMessage());
314             String warn = ""
315                     + "\n\n\t!!!\n\tTo avoid this error, place an appropriate JDBC driver file for serviceName '{}' in addons directory.\n"
316                     + "\tCopy missing JDBC-Driver-jar to your openHab/addons Folder.\n\t!!!\n" + "\tDOWNLOAD: \n";
317             String serviceName = this.serviceName;
318             if (serviceName != null) {
319                 switch (serviceName) {
320                     case "derby":
321                         warn += "\tDerby:     version >= 10.14.2.0 from          https://mvnrepository.com/artifact/org.apache.derby/derby\n";
322                         break;
323                     case "h2":
324                         warn += "\tH2:        version >= 1.4.189 from            https://mvnrepository.com/artifact/com.h2database/h2\n";
325                         break;
326                     case "hsqldb":
327                         warn += "\tHSQLDB:    version >= 2.3.3 from              https://mvnrepository.com/artifact/org.hsqldb/hsqldb\n";
328                         break;
329                     case "mariadb":
330                         warn += "\tMariaDB:   version >= 3.0.8 from              https://mvnrepository.com/artifact/org.mariadb.jdbc/mariadb-java-client\n";
331                         break;
332                     case "mysql":
333                         warn += "\tMySQL:     version >= 8.0.33 from             https://mvnrepository.com/artifact/com.mysql/mysql-connector-j\n";
334                         break;
335                     case "postgresql":
336                         warn += "\tPostgreSQL:version >= 42.4.3 from             https://mvnrepository.com/artifact/org.postgresql/postgresql\n";
337                         break;
338                     case "sqlite":
339                         warn += "\tSQLite:    version >= 3.40.0.0 from           https://mvnrepository.com/artifact/org.xerial/sqlite-jdbc\n";
340                         break;
341                 }
342             }
343             logger.warn(warn, serviceName);
344         }
345     }
346
347     public Properties getHikariConfiguration() {
348         return dBDAO.getConnectionProperties();
349     }
350
351     public String getName() {
352         // return serviceName;
353         return name;
354     }
355
356     public @Nullable String getServiceName() {
357         return serviceName;
358     }
359
360     public String getItemsManageTable() {
361         return itemsManageTable;
362     }
363
364     public String getTableNamePrefix() {
365         return tableNamePrefix;
366     }
367
368     public int getErrReconnectThreshold() {
369         return errReconnectThreshold;
370     }
371
372     public boolean getRebuildTableNames() {
373         return rebuildTableNames;
374     }
375
376     public int getNumberDecimalcount() {
377         return numberDecimalcount;
378     }
379
380     public boolean getTableUseRealItemNames() {
381         return tableUseRealItemNames;
382     }
383
384     public boolean getTableCaseSensitiveItemNames() {
385         return tableCaseSensitiveItemNames;
386     }
387
388     /**
389      * Checks if real item names (without number suffix) is enabled.
390      *
391      * @return true if both tableUseRealItemNames and tableCaseSensitiveItemNames are enabled.
392      */
393     public boolean getTableUseRealCaseSensitiveItemNames() {
394         return tableUseRealItemNames && tableCaseSensitiveItemNames;
395     }
396
397     public int getTableIdDigitCount() {
398         return tableIdDigitCount;
399     }
400
401     public JdbcBaseDAO getDBDAO() {
402         return dBDAO;
403     }
404
405     public @Nullable String getDbName() {
406         return dbName;
407     }
408
409     public void setDbName(String dbName) {
410         this.dbName = dbName;
411     }
412
413     public boolean isDbConnected() {
414         return dbConnected;
415     }
416
417     public void setDbConnected(boolean dbConnected) {
418         logger.debug("JDBC::setDbConnected {}", dbConnected);
419         // Initializing step, after db is connected.
420         // Initialize sqlTypes, depending on DB version for example
421         dBDAO.initAfterFirstDbConnection();
422         // Running once again to prior external configured SqlTypes!
423         setSqlTypes();
424         this.dbConnected = dbConnected;
425     }
426
427     public boolean isDriverAvailable() {
428         return driverAvailable;
429     }
430 }