]> git.basschouten.com Git - openhab-addons.git/blob
f74db9cd228e581af976081009b462fd47ae1eb2
[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.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.Nullable;
25 import org.openhab.persistence.jdbc.db.JdbcBaseDAO;
26 import org.openhab.persistence.jdbc.utils.MovingAverage;
27 import org.openhab.persistence.jdbc.utils.StringUtilsExt;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
30
31 /**
32  * Configuration class
33  *
34  * @author Helmut Lehmeyer - Initial contribution
35  */
36 public class JdbcConfiguration {
37     private final Logger logger = LoggerFactory.getLogger(JdbcConfiguration.class);
38
39     private static final Pattern EXTRACT_CONFIG_PATTERN = Pattern.compile("^(.*?)\\.([0-9.a-zA-Z]+)$");
40     private static final String DB_DAO_PACKAGE = "org.openhab.persistence.jdbc.db.Jdbc";
41
42     private Map<Object, Object> configuration;
43
44     private JdbcBaseDAO dBDAO = null;
45     private String dbName = null;
46     boolean dbConnected = false;
47     boolean driverAvailable = false;
48
49     private String serviceName;
50     private String name = "jdbc";
51     public final boolean valid;
52
53     // private String url;
54     // private String user;
55     // private String password;
56     private int numberDecimalcount = 3;
57     private boolean tableUseRealItemNames = false;
58     private String tableNamePrefix = "item";
59     private int tableIdDigitCount = 4;
60     private boolean rebuildTableNames = false;
61
62     private int errReconnectThreshold = 0;
63
64     public int timerCount = 0;
65     public int time1000Statements = 0;
66     public long timer1000 = 0;
67     public MovingAverage timeAverage50arr = new MovingAverage(50);
68     public MovingAverage timeAverage100arr = new MovingAverage(100);
69     public MovingAverage timeAverage200arr = new MovingAverage(200);
70     public boolean enableLogTime = false;
71
72     public JdbcConfiguration(Map<Object, Object> configuration) {
73         logger.debug("JDBC::JdbcConfiguration");
74         valid = updateConfig(configuration);
75     }
76
77     private boolean updateConfig(Map<Object, @Nullable Object> config) {
78         configuration = config;
79
80         logger.debug("JDBC::updateConfig: configuration size = {}", configuration.size());
81
82         String user = (String) configuration.get("user");
83         String password = (String) configuration.get("password");
84
85         // mandatory url
86         String url = (String) configuration.get("url");
87
88         if (url == null) {
89             logger.error("Mandatory url parameter is missing in configuration!");
90             return false;
91         }
92
93         Properties parsedURL = StringUtilsExt.parseJdbcURL(url);
94
95         if (user == null || user.isBlank()) {
96             logger.debug("No jdbc:user parameter defined in jdbc.cfg");
97         }
98         if (password == null || password.isBlank()) {
99             logger.debug("No jdbc:password parameter defined in jdbc.cfg.");
100         }
101
102         if (url.isBlank()) {
103             logger.debug(
104                     "JDBC url is missing - please configure in jdbc.cfg like 'jdbc:<service>:<host>[:<port>;<attributes>]'");
105             return false;
106         }
107
108         if ("false".equalsIgnoreCase(parsedURL.getProperty("parseValid"))) {
109             Enumeration<?> en = parsedURL.propertyNames();
110             String enstr = "";
111             for (Object key : Collections.list(en)) {
112                 enstr += key + " = " + parsedURL.getProperty("" + key) + "\n";
113             }
114             logger.warn(
115                     "JDBC url is not well formatted: {}\nPlease configure in openhab.cfg like 'jdbc:<service>:<host>[:<port>;<attributes>]'",
116                     enstr);
117             return false;
118         }
119
120         logger.debug("JDBC::updateConfig: user={}", user);
121         logger.debug("JDBC::updateConfig: password exists? {}", password != null && !password.isBlank());
122         logger.debug("JDBC::updateConfig: url={}", url);
123
124         // set database type and database type class
125         setDBDAOClass(parsedURL.getProperty("dbShortcut")); // derby, h2, hsqldb, mariadb, mysql, postgresql,
126                                                             // sqlite
127         // set user
128         if (user != null && !user.isBlank()) {
129             dBDAO.databaseProps.setProperty("dataSource.user", user);
130         }
131
132         // set password
133         if (password != null && !password.isBlank()) {
134             dBDAO.databaseProps.setProperty("dataSource.password", password);
135         }
136
137         // set sql-types from external config
138         setSqlTypes();
139
140         final Pattern isNumericPattern = Pattern.compile("\\d+(\\.\\d+)?");
141         String et = (String) configuration.get("reconnectCnt");
142         if (et != null && !et.isBlank() && isNumericPattern.matcher(et).matches()) {
143             errReconnectThreshold = Integer.parseInt(et);
144             logger.debug("JDBC::updateConfig: errReconnectThreshold={}", errReconnectThreshold);
145         }
146
147         String np = (String) configuration.get("tableNamePrefix");
148         if (np != null && !np.isBlank()) {
149             tableNamePrefix = np;
150             logger.debug("JDBC::updateConfig: tableNamePrefix={}", tableNamePrefix);
151         }
152
153         String dd = (String) configuration.get("numberDecimalcount");
154         if (dd != null && !dd.isBlank() && isNumericPattern.matcher(dd).matches()) {
155             numberDecimalcount = Integer.parseInt(dd);
156             logger.debug("JDBC::updateConfig: numberDecimalcount={}", numberDecimalcount);
157         }
158
159         String rn = (String) configuration.get("tableUseRealItemNames");
160         if (rn != null && !rn.isBlank()) {
161             tableUseRealItemNames = "true".equals(rn) ? Boolean.parseBoolean(rn) : false;
162             logger.debug("JDBC::updateConfig: tableUseRealItemNames={}", tableUseRealItemNames);
163         }
164
165         String td = (String) configuration.get("tableIdDigitCount");
166         if (td != null && !td.isBlank() && isNumericPattern.matcher(td).matches()) {
167             tableIdDigitCount = Integer.parseInt(td);
168             logger.debug("JDBC::updateConfig: tableIdDigitCount={}", tableIdDigitCount);
169         }
170
171         String rt = (String) configuration.get("rebuildTableNames");
172         if (rt != null && !rt.isBlank()) {
173             rebuildTableNames = Boolean.parseBoolean(rt);
174             logger.debug("JDBC::updateConfig: rebuildTableNames={}", rebuildTableNames);
175         }
176
177         // undocumented
178         String ac = (String) configuration.get("maximumPoolSize");
179         if (ac != null && !ac.isBlank()) {
180             dBDAO.databaseProps.setProperty("maximumPoolSize", ac);
181         }
182
183         // undocumented
184         String ic = (String) configuration.get("minimumIdle");
185         if (ic != null && !ic.isBlank()) {
186             dBDAO.databaseProps.setProperty("minimumIdle", ic);
187         }
188
189         // undocumented
190         String it = (String) configuration.get("idleTimeout");
191         if (it != null && !it.isBlank()) {
192             dBDAO.databaseProps.setProperty("idleTimeout", it);
193         }
194         // undocumented
195         String ent = (String) configuration.get("enableLogTime");
196         if (ent != null && !ent.isBlank()) {
197             enableLogTime = "true".equals(ent) ? Boolean.parseBoolean(ent) : false;
198         }
199         logger.debug("JDBC::updateConfig: enableLogTime {}", enableLogTime);
200
201         // undocumented
202         String fd = (String) configuration.get("driverClassName");
203         if (fd != null && !fd.isBlank()) {
204             dBDAO.databaseProps.setProperty("driverClassName", fd);
205         }
206
207         // undocumented
208         String ds = (String) configuration.get("dataSourceClassName");
209         if (ds != null && !ds.isBlank()) {
210             dBDAO.databaseProps.setProperty("dataSourceClassName", ds);
211         }
212
213         // undocumented
214         String dn = dBDAO.databaseProps.getProperty("driverClassName");
215         if (dn == null) {
216             dn = dBDAO.databaseProps.getProperty("dataSourceClassName");
217         } else {
218             dBDAO.databaseProps.setProperty("jdbcUrl", url);
219         }
220
221         // test if JDBC driver bundle is available
222         testJDBCDriver(dn);
223
224         logger.debug("JDBC::updateConfig: configuration complete. service={}", getName());
225
226         return true;
227     }
228
229     private void setDBDAOClass(String sn) {
230         serviceName = "none";
231
232         // set database type
233         if (sn == null || sn.isBlank() || sn.length() < 2) {
234             logger.error(
235                     "JDBC::updateConfig: Required database url like 'jdbc:<service>:<host>[:<port>;<attributes>]' - please configure the jdbc:url parameter in openhab.cfg");
236         } else {
237             serviceName = sn;
238         }
239         logger.debug("JDBC::updateConfig: found serviceName = '{}'", serviceName);
240
241         // set class for database type
242         String ddp = DB_DAO_PACKAGE + serviceName.toUpperCase().charAt(0) + serviceName.toLowerCase().substring(1)
243                 + "DAO";
244
245         logger.debug("JDBC::updateConfig: Init Data Access Object Class: '{}'", ddp);
246         try {
247             dBDAO = (JdbcBaseDAO) Class.forName(ddp).getConstructor().newInstance();
248             logger.debug("JDBC::updateConfig: dBDAO ClassName={}", dBDAO.getClass().getName());
249         } catch (IllegalAccessException | InstantiationException | InvocationTargetException
250                 | NoSuchMethodException e) {
251             logger.error("JDBC::updateConfig: Exception: {}", 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          https://mvnrepository.com/artifact/org.apache.derby/derby\n";
300             } else if (serviceName.equals("h2")) {
301                 warn += "\tH2:        version >= 1.4.189 from            https://mvnrepository.com/artifact/com.h2database/h2\n";
302             } else if (serviceName.equals("hsqldb")) {
303                 warn += "\tHSQLDB:    version >= 2.3.3 from              https://mvnrepository.com/artifact/org.hsqldb/hsqldb\n";
304             } else if (serviceName.equals("mariadb")) {
305                 warn += "\tMariaDB:   version >= 1.2.0 from              https://mvnrepository.com/artifact/org.mariadb.jdbc/mariadb-java-client\n";
306             } else if (serviceName.equals("mysql")) {
307                 warn += "\tMySQL:     version >= 5.1.36 from             https://mvnrepository.com/artifact/mysql/mysql-connector-java\n";
308             } else if (serviceName.equals("postgresql")) {
309                 warn += "\tPostgreSQL:version >= 9.4.1208 from           https://mvnrepository.com/artifact/org.postgresql/postgresql\n";
310             } else if (serviceName.equals("sqlite")) {
311                 warn += "\tSQLite:    version >= 3.16.1 from             https://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 }