]> git.basschouten.com Git - openhab-addons.git/blob
f3aa81a4dc07198d0ef18afb680522f9589594d5
[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.sql.SQLInvalidAuthorizationSpecException;
16 import java.time.ZonedDateTime;
17 import java.util.ArrayList;
18 import java.util.HashMap;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.Set;
22 import java.util.stream.Collectors;
23
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.knowm.yank.Yank;
27 import org.openhab.core.i18n.TimeZoneProvider;
28 import org.openhab.core.items.Item;
29 import org.openhab.core.persistence.FilterCriteria;
30 import org.openhab.core.persistence.HistoricItem;
31 import org.openhab.core.persistence.PersistenceItemInfo;
32 import org.openhab.core.types.State;
33 import org.openhab.persistence.jdbc.dto.ItemVO;
34 import org.openhab.persistence.jdbc.dto.ItemsVO;
35 import org.openhab.persistence.jdbc.dto.JdbcPersistenceItemInfo;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38
39 import com.zaxxer.hikari.pool.HikariPool.PoolInitializationException;
40
41 /**
42  * Mapper class
43  *
44  * @author Helmut Lehmeyer - Initial contribution
45  */
46 @NonNullByDefault
47 public class JdbcMapper {
48     private final Logger logger = LoggerFactory.getLogger(JdbcMapper.class);
49
50     private final TimeZoneProvider timeZoneProvider;
51
52     // Error counter - used to reconnect to database on error
53     protected int errCnt;
54     protected boolean initialized = false;
55     protected @NonNullByDefault({}) JdbcConfiguration conf;
56     protected final Map<String, String> sqlTables = new HashMap<>();
57     private long afterAccessMin = 10000;
58     private long afterAccessMax = 0;
59     private static final String ITEM_NAME_PATTERN = "[^a-zA-Z_0-9\\-]";
60
61     public JdbcMapper(TimeZoneProvider timeZoneProvider) {
62         this.timeZoneProvider = timeZoneProvider;
63     }
64
65     /*****************
66      * MAPPER ITEMS *
67      *****************/
68     public boolean pingDB() {
69         logger.debug("JDBC::pingDB");
70         boolean ret = false;
71         long timerStart = System.currentTimeMillis();
72         if (openConnection()) {
73             if (conf.getDbName() == null) {
74                 logger.debug(
75                         "JDBC::pingDB asking db for name as absolutely first db action, after connection is established.");
76                 String dbName = conf.getDBDAO().doGetDB();
77                 if (dbName == null) {
78                     ret = false;
79                 } else {
80                     conf.setDbName(dbName);
81                     ret = dbName.length() > 0;
82                 }
83             } else {
84                 final @Nullable Integer result = conf.getDBDAO().doPingDB();
85                 ret = result != null && result > 0;
86             }
87         }
88         logTime("pingDB", timerStart, System.currentTimeMillis());
89         return ret;
90     }
91
92     public String getDB() {
93         logger.debug("JDBC::getDB");
94         long timerStart = System.currentTimeMillis();
95         String res = conf.getDBDAO().doGetDB();
96         logTime("getDB", timerStart, System.currentTimeMillis());
97         return res != null ? res : "";
98     }
99
100     public ItemsVO createNewEntryInItemsTable(ItemsVO vo) {
101         logger.debug("JDBC::createNewEntryInItemsTable");
102         long timerStart = System.currentTimeMillis();
103         Long i = conf.getDBDAO().doCreateNewEntryInItemsTable(vo);
104         vo.setItemid(i.intValue());
105         logTime("doCreateNewEntryInItemsTable", timerStart, System.currentTimeMillis());
106         return vo;
107     }
108
109     public boolean createItemsTableIfNot(ItemsVO vo) {
110         logger.debug("JDBC::createItemsTableIfNot");
111         long timerStart = System.currentTimeMillis();
112         conf.getDBDAO().doCreateItemsTableIfNot(vo);
113         logTime("doCreateItemsTableIfNot", timerStart, System.currentTimeMillis());
114         return true;
115     }
116
117     public ItemsVO deleteItemsEntry(ItemsVO vo) {
118         logger.debug("JDBC::deleteItemsEntry");
119         long timerStart = System.currentTimeMillis();
120         conf.getDBDAO().doDeleteItemsEntry(vo);
121         logTime("deleteItemsEntry", timerStart, System.currentTimeMillis());
122         return vo;
123     }
124
125     public List<ItemsVO> getItemIDTableNames() {
126         logger.debug("JDBC::getItemIDTableNames");
127         long timerStart = System.currentTimeMillis();
128         List<ItemsVO> vo = conf.getDBDAO().doGetItemIDTableNames(new ItemsVO());
129         logTime("getItemIDTableNames", timerStart, System.currentTimeMillis());
130         return vo;
131     }
132
133     public List<ItemsVO> getItemTables() {
134         logger.debug("JDBC::getItemTables");
135         long timerStart = System.currentTimeMillis();
136         ItemsVO vo = new ItemsVO();
137         vo.setJdbcUriDatabaseName(conf.getDbName());
138         List<ItemsVO> vol = conf.getDBDAO().doGetItemTables(vo);
139         logTime("getItemTables", timerStart, System.currentTimeMillis());
140         return vol;
141     }
142
143     /****************
144      * MAPPERS ITEM *
145      ****************/
146     public void updateItemTableNames(List<ItemVO> vol) {
147         logger.debug("JDBC::updateItemTableNames");
148         long timerStart = System.currentTimeMillis();
149         conf.getDBDAO().doUpdateItemTableNames(vol);
150         logTime("updateItemTableNames", timerStart, System.currentTimeMillis());
151     }
152
153     public ItemVO createItemTable(ItemVO vo) {
154         logger.debug("JDBC::createItemTable");
155         long timerStart = System.currentTimeMillis();
156         conf.getDBDAO().doCreateItemTable(vo);
157         logTime("createItemTable", timerStart, System.currentTimeMillis());
158         return vo;
159     }
160
161     public Item storeItemValue(Item item, State itemState, @Nullable ZonedDateTime date) {
162         logger.debug("JDBC::storeItemValue: item={} state={} date={}", item, itemState, date);
163         String tableName = getTable(item);
164         long timerStart = System.currentTimeMillis();
165         if (date == null) {
166             conf.getDBDAO().doStoreItemValue(item, itemState, new ItemVO(tableName, null));
167         } else {
168             conf.getDBDAO().doStoreItemValue(item, itemState, new ItemVO(tableName, null), date);
169         }
170         logTime("storeItemValue", timerStart, System.currentTimeMillis());
171         errCnt = 0;
172         return item;
173     }
174
175     public List<HistoricItem> getHistItemFilterQuery(FilterCriteria filter, int numberDecimalcount, String table,
176             Item item) {
177         logger.debug(
178                 "JDBC::getHistItemFilterQuery filter='{}' numberDecimalcount='{}' table='{}' item='{}' itemName='{}'",
179                 true, numberDecimalcount, table, item, item.getName());
180         long timerStart = System.currentTimeMillis();
181         List<HistoricItem> result = conf.getDBDAO().doGetHistItemFilterQuery(item, filter, numberDecimalcount, table,
182                 item.getName(), timeZoneProvider.getTimeZone());
183         logTime("getHistItemFilterQuery", timerStart, System.currentTimeMillis());
184         errCnt = 0;
185         return result;
186     }
187
188     public boolean deleteItemValues(FilterCriteria filter, String table) {
189         logger.debug("JDBC::deleteItemValues filter='{}' table='{}' itemName='{}'", true, table, filter.getItemName());
190         long timerStart = System.currentTimeMillis();
191         conf.getDBDAO().doDeleteItemValues(filter, table, timeZoneProvider.getTimeZone());
192         logTime("deleteItemValues", timerStart, System.currentTimeMillis());
193         errCnt = 0;
194         return true;
195     }
196
197     /***********************
198      * DATABASE CONNECTION *
199      ***********************/
200     protected boolean openConnection() {
201         logger.debug("JDBC::openConnection isDriverAvailable: {}", conf.isDriverAvailable());
202         if (conf.isDriverAvailable() && !conf.isDbConnected()) {
203             logger.info("JDBC::openConnection: Driver is available::Yank setupDataSource");
204             try {
205                 Yank.setupDefaultConnectionPool(conf.getHikariConfiguration());
206                 conf.setDbConnected(true);
207                 return true;
208             } catch (PoolInitializationException e) {
209                 Throwable cause = e.getCause();
210                 if (cause instanceof SQLInvalidAuthorizationSpecException) {
211                     logger.warn("JDBC::openConnection: failed to open connection: {}", cause.getMessage());
212                 } else {
213                     logger.warn("JDBC::openConnection: failed to open connection: {}", e.getMessage());
214                 }
215                 initialized = false;
216                 return false;
217             }
218         } else if (!conf.isDriverAvailable()) {
219             logger.warn("JDBC::openConnection: no driver available!");
220             initialized = false;
221             return false;
222         }
223         return true;
224     }
225
226     protected void closeConnection() {
227         logger.debug("JDBC::closeConnection");
228         // Closes all open connection pools
229         Yank.releaseDefaultConnectionPool();
230         conf.setDbConnected(false);
231     }
232
233     protected boolean checkDBAccessability() {
234         // Check if connection is valid
235         if (initialized) {
236             return true;
237         }
238         // first
239         boolean p = pingDB();
240         if (p) {
241             logger.debug("JDBC::checkDBAcessability, first try connection: {}", p);
242             return (p && !(conf.getErrReconnectThreshold() > 0 && errCnt <= conf.getErrReconnectThreshold()));
243         } else {
244             // second
245             p = pingDB();
246             logger.debug("JDBC::checkDBAcessability, second try connection: {}", p);
247             return (p && !(conf.getErrReconnectThreshold() > 0 && errCnt <= conf.getErrReconnectThreshold()));
248         }
249     }
250
251     /**************************
252      * DATABASE TABLEHANDLING *
253      **************************/
254     protected void checkDBSchema() {
255         // Create Items Table if does not exist
256         createItemsTableIfNot(new ItemsVO());
257         if (conf.getRebuildTableNames()) {
258             formatTableNames();
259             logger.info(
260                     "JDBC::checkDBSchema: Rebuild complete, configure the 'rebuildTableNames' setting to 'false' to stop rebuilds on startup");
261         } else {
262             // Reset the error counter
263             errCnt = 0;
264             for (ItemsVO vo : getItemIDTableNames()) {
265                 sqlTables.put(vo.getItemname(), getTableName(vo.getItemid(), vo.getItemname()));
266             }
267         }
268     }
269
270     protected String getTable(Item item) {
271         int rowId = 0;
272         ItemsVO isvo;
273         ItemVO ivo;
274
275         String itemName = item.getName();
276         String tableName = sqlTables.get(itemName);
277
278         // Table already exists - return the name
279         if (tableName != null) {
280             return tableName;
281         }
282
283         logger.debug("JDBC::getTable: no table found for item '{}' in sqlTables", itemName);
284
285         // Create a new entry in items table
286         isvo = new ItemsVO();
287         isvo.setItemname(itemName);
288         isvo = createNewEntryInItemsTable(isvo);
289         rowId = isvo.getItemid();
290         if (rowId == 0) {
291             logger.error("JDBC::getTable: Creating table for item '{}' failed.", itemName);
292         }
293         // Create the table name
294         logger.debug("JDBC::getTable: getTableName with rowId={} itemName={}", rowId, itemName);
295         tableName = getTableName(rowId, itemName);
296
297         // Create table for item
298         String dataType = conf.getDBDAO().getDataType(item);
299         ivo = new ItemVO(tableName, itemName);
300         ivo.setDbType(dataType);
301         ivo = createItemTable(ivo);
302         logger.debug("JDBC::getTable: Table created for item '{}' with dataType {} in SQL database.", itemName,
303                 dataType);
304         sqlTables.put(itemName, tableName);
305
306         // Check if the new entry is in the table list
307         // If it's not in the list, then there was an error and we need to do
308         // some tidying up
309         // The item needs to be removed from the index table to avoid duplicates
310         if (sqlTables.get(itemName) == null) {
311             logger.error("JDBC::getTable: Item '{}' was not added to the table - removing index", itemName);
312             isvo = new ItemsVO();
313             isvo.setItemname(itemName);
314             deleteItemsEntry(isvo);
315         }
316
317         return tableName;
318     }
319
320     private void formatTableNames() {
321         boolean tmpinit = initialized;
322         if (tmpinit) {
323             initialized = false;
324         }
325
326         Map<Integer, String> tableIds = new HashMap<>();
327
328         //
329         for (ItemsVO vo : getItemIDTableNames()) {
330             String t = getTableName(vo.getItemid(), vo.getItemname());
331             sqlTables.put(vo.getItemname(), t);
332             tableIds.put(vo.getItemid(), t);
333         }
334
335         //
336         List<ItemsVO> al = getItemTables();
337
338         String oldName = "";
339         String newName = "";
340         List<ItemVO> oldNewTablenames = new ArrayList<>();
341         for (int i = 0; i < al.size(); i++) {
342             int id = -1;
343             oldName = al.get(i).getTable_name();
344             logger.info("JDBC::formatTableNames: found Table Name= {}", oldName);
345
346             if (oldName.startsWith(conf.getTableNamePrefix()) && !oldName.contains("_")) {
347                 id = Integer.parseInt(oldName.substring(conf.getTableNamePrefix().length()));
348                 logger.info("JDBC::formatTableNames: found Table with Prefix '{}' Name= {} id= {}",
349                         conf.getTableNamePrefix(), oldName, (id));
350             } else if (oldName.contains("_")) {
351                 id = Integer.parseInt(oldName.substring(oldName.lastIndexOf("_") + 1));
352                 logger.info("JDBC::formatTableNames: found Table Name= {} id= {}", oldName, (id));
353             }
354             logger.info("JDBC::formatTableNames: found Table id= {}", id);
355
356             newName = tableIds.get(id);
357             logger.info("JDBC::formatTableNames: found Table newName= {}", newName);
358
359             if (newName != null) {
360                 if (!oldName.equalsIgnoreCase(newName)) {
361                     oldNewTablenames.add(new ItemVO(oldName, newName));
362                     logger.info("JDBC::formatTableNames: Table '{}' will be renamed to '{}'", oldName, newName);
363                 } else {
364                     logger.info("JDBC::formatTableNames: Table oldName='{}' newName='{}' nothing to rename", oldName,
365                             newName);
366                 }
367             } else {
368                 logger.error("JDBC::formatTableNames: Table '{}' could NOT be renamed to '{}'", oldName, newName);
369                 break;
370             }
371         }
372
373         updateItemTableNames(oldNewTablenames);
374         logger.info("JDBC::formatTableNames: Finished updating {} item table names", oldNewTablenames.size());
375
376         initialized = tmpinit;
377     }
378
379     private String getTableName(int rowId, String itemName) {
380         return getTableNamePrefix(itemName) + formatRight(rowId, conf.getTableIdDigitCount());
381     }
382
383     private String getTableNamePrefix(String itemName) {
384         String name = conf.getTableNamePrefix();
385         if (conf.getTableUseRealItemNames()) {
386             // Create the table name with real Item Names
387             name = (itemName.replaceAll(ITEM_NAME_PATTERN, "") + "_").toLowerCase();
388         }
389         return name;
390     }
391
392     public Set<PersistenceItemInfo> getItems() {
393         // TODO: in general it would be possible to query the count, earliest and latest values for each item too but it
394         // would be a very costly operation
395         return sqlTables.keySet().stream().map(itemName -> new JdbcPersistenceItemInfo(itemName))
396                 .collect(Collectors.<PersistenceItemInfo> toSet());
397     }
398
399     private static String formatRight(final Object value, final int len) {
400         final String valueAsString = String.valueOf(value);
401         if (valueAsString.length() < len) {
402             final StringBuffer result = new StringBuffer(len);
403             for (int i = len - valueAsString.length(); i > 0; i--) {
404                 result.append('0');
405             }
406             result.append(valueAsString);
407             return result.toString();
408         } else {
409             return valueAsString;
410         }
411     }
412
413     /*****************
414      * H E L P E R S *
415      *****************/
416     private void logTime(String me, long timerStart, long timerStop) {
417         if (conf.enableLogTime && logger.isInfoEnabled()) {
418             conf.timerCount++;
419             int timerDiff = (int) (timerStop - timerStart);
420             if (timerDiff < afterAccessMin) {
421                 afterAccessMin = timerDiff;
422             }
423             if (timerDiff > afterAccessMax) {
424                 afterAccessMax = timerDiff;
425             }
426             conf.timeAverage50arr.add(timerDiff);
427             conf.timeAverage100arr.add(timerDiff);
428             conf.timeAverage200arr.add(timerDiff);
429             if (conf.timerCount == 1) {
430                 conf.timer1000 = System.currentTimeMillis();
431             }
432             if (conf.timerCount == 1001) {
433                 conf.time1000Statements = Math.round(((int) (System.currentTimeMillis() - conf.timer1000)) / 1000);// Seconds
434                 conf.timerCount = 0;
435             }
436             logger.info(
437                     "JDBC::logTime: '{}':\n afterAccess     = {} ms\n timeAverage50  = {} ms\n timeAverage100 = {} ms\n timeAverage200 = {} ms\n afterAccessMin  = {} ms\n afterAccessMax  = {} ms\n 1000Statements = {} sec\n statementCount = {}\n",
438                     me, timerDiff, conf.timeAverage50arr.getAverageInteger(),
439                     conf.timeAverage100arr.getAverageInteger(), conf.timeAverage200arr.getAverageInteger(),
440                     afterAccessMin, afterAccessMax, conf.time1000Statements, conf.timerCount);
441         }
442     }
443 }