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