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