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