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