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