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