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