]> git.basschouten.com Git - openhab-addons.git/blob
05f3a8975b5e968cedcdbd4365edd44199100571
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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.util.Collections;
16 import java.util.Enumeration;
17 import java.util.Map;
18 import java.util.Properties;
19 import java.util.Set;
20 import java.util.regex.Matcher;
21 import java.util.regex.Pattern;
22
23 import org.eclipse.jdt.annotation.Nullable;
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, @Nullable 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).newInstance();
247             logger.debug("JDBC::updateConfig: dBDAO ClassName={}", dBDAO.getClass().getName());
248         } catch (InstantiationException e) {
249             logger.error("JDBC::updateConfig: InstantiationException: {}", e.getMessage());
250         } catch (IllegalAccessException e) {
251             logger.error("JDBC::updateConfig: IllegalAccessException: {}", e.getMessage());
252         } catch (ClassNotFoundException e) {
253             logger.warn("JDBC::updateConfig: no Configuration for serviceName '{}' found. ClassNotFoundException: {}",
254                     serviceName, e.getMessage());
255             logger.debug("JDBC::updateConfig: using default Database Configuration: JdbcBaseDAO !!");
256             dBDAO = new JdbcBaseDAO();
257             logger.debug("JDBC::updateConfig: dBConfig done");
258         }
259     }
260
261     private void setSqlTypes() {
262         Set<Object> keys = configuration.keySet();
263
264         for (Object k : keys) {
265             String key = (String) k;
266             Matcher matcher = EXTRACT_CONFIG_PATTERN.matcher(key);
267             if (!matcher.matches()) {
268                 continue;
269             }
270             matcher.reset();
271             matcher.find();
272             if (!matcher.group(1).equals("sqltype")) {
273                 continue;
274             }
275             String itemType = matcher.group(2);
276             if (!itemType.startsWith("table")) {
277                 itemType = itemType.toUpperCase() + "ITEM";
278             }
279             String value = (String) configuration.get(key);
280             logger.debug("JDBC::updateConfig: set sqlTypes: itemType={} value={}", itemType, value);
281             dBDAO.sqlTypes.put(itemType, value);
282         }
283     }
284
285     private void testJDBCDriver(String driver) {
286         driverAvailable = true;
287         try {
288             Class.forName(driver);
289             logger.debug("JDBC::updateConfig: load JDBC-driverClass was successful: '{}'", driver);
290         } catch (ClassNotFoundException e) {
291             driverAvailable = false;
292             logger.error(
293                     "JDBC::updateConfig: could NOT load JDBC-driverClassName or JDBC-dataSourceClassName. ClassNotFoundException: '{}'",
294                     e.getMessage());
295             String warn = ""
296                     + "\n\n\t!!!\n\tTo avoid this error, place an appropriate JDBC driver file for serviceName '{}' in addons directory.\n"
297                     + "\tCopy missing JDBC-Driver-jar to your OpenHab/addons Folder.\n\t!!!\n" + "\tDOWNLOAD: \n";
298             if (serviceName.equals("derby")) {
299                 warn += "\tDerby:     version >= 10.11.1.1 from          http://mvnrepository.com/artifact/org.apache.derby/derby\n";
300             } else if (serviceName.equals("h2")) {
301                 warn += "\tH2:        version >= 1.4.189 from            http://mvnrepository.com/artifact/com.h2database/h2\n";
302             } else if (serviceName.equals("hsqldb")) {
303                 warn += "\tHSQLDB:    version >= 2.3.3 from              http://mvnrepository.com/artifact/org.hsqldb/hsqldb\n";
304             } else if (serviceName.equals("mariadb")) {
305                 warn += "\tMariaDB:   version >= 1.2.0 from              http://mvnrepository.com/artifact/org.mariadb.jdbc/mariadb-java-client\n";
306             } else if (serviceName.equals("mysql")) {
307                 warn += "\tMySQL:     version >= 5.1.36 from             http://mvnrepository.com/artifact/mysql/mysql-connector-java\n";
308             } else if (serviceName.equals("postgresql")) {
309                 warn += "\tPostgreSQL:version >= 9.4.1208 from    http://mvnrepository.com/artifact/org.postgresql/postgresql\n";
310             } else if (serviceName.equals("sqlite")) {
311                 warn += "\tSQLite:    version >= 3.16.1 from           http://mvnrepository.com/artifact/org.xerial/sqlite-jdbc\n";
312             }
313             logger.warn(warn, serviceName);
314         }
315     }
316
317     public Properties getHikariConfiguration() {
318         return dBDAO.databaseProps;
319     }
320
321     public String getName() {
322         // return serviceName;
323         return name;
324     }
325
326     public String getServiceName() {
327         return serviceName;
328     }
329
330     public String getTableNamePrefix() {
331         return tableNamePrefix;
332     }
333
334     public int getErrReconnectThreshold() {
335         return errReconnectThreshold;
336     }
337
338     public boolean getRebuildTableNames() {
339         return rebuildTableNames;
340     }
341
342     public int getNumberDecimalcount() {
343         return numberDecimalcount;
344     }
345
346     public boolean getTableUseRealItemNames() {
347         return tableUseRealItemNames;
348     }
349
350     public int getTableIdDigitCount() {
351         return tableIdDigitCount;
352     }
353
354     public JdbcBaseDAO getDBDAO() {
355         return dBDAO;
356     }
357
358     public String getDbName() {
359         return dbName;
360     }
361
362     public void setDbName(String dbName) {
363         this.dbName = dbName;
364     }
365
366     public boolean isDbConnected() {
367         return dbConnected;
368     }
369
370     public void setDbConnected(boolean dbConnected) {
371         logger.debug("JDBC::setDbConnected {}", dbConnected);
372         // Initializing step, after db is connected.
373         // Initialize sqlTypes, depending on DB version for example
374         dBDAO.initAfterFirstDbConnection();
375         // Running once again to prior external configured SqlTypes!
376         setSqlTypes();
377         this.dbConnected = dbConnected;
378     }
379
380     public boolean isDriverAvailable() {
381         return driverAvailable;
382     }
383 }