2 * Copyright (c) 2010-2021 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.persistence.jdbc.internal;
15 import java.util.ArrayList;
16 import java.util.HashMap;
17 import java.util.List;
20 import java.util.stream.Collectors;
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;
37 * @author Helmut Lehmeyer - Initial contribution
39 public class JdbcMapper {
40 private final Logger logger = LoggerFactory.getLogger(JdbcMapper.class);
42 private final TimeZoneProvider timeZoneProvider;
44 // Error counter - used to reconnect to database on error
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\\-]";
53 public JdbcMapper(TimeZoneProvider timeZoneProvider) {
54 this.timeZoneProvider = timeZoneProvider;
60 public boolean pingDB() {
61 logger.debug("JDBC::pingDB");
63 long timerStart = System.currentTimeMillis();
64 if (openConnection()) {
65 if (conf.getDbName() == null) {
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;
72 ret = conf.getDBDAO().doPingDB() > 0;
75 logTime("pingDB", timerStart, System.currentTimeMillis());
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());
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());
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());
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());
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());
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());
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());
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());
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());
155 long timerStart = System.currentTimeMillis();
156 conf.getDBDAO().doStoreItemValue(item, new ItemVO(tableName, null));
157 logTime("storeItemValue", timerStart, System.currentTimeMillis());
162 public List<HistoricItem> getHistItemFilterQuery(FilterCriteria filter, int numberDecimalcount, String table,
165 "JDBC::getHistItemFilterQuery filter='{}' numberDecimalcount='{}' table='{}' item='{}' itemName='{}'",
166 (filter != null), numberDecimalcount, table, item, item.getName());
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());
174 logger.error("JDBC::getHistItemFilterQuery: TABLE is NULL; cannot get data from non-existent table.");
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);
189 } else if (!conf.isDriverAvailable()) {
190 logger.warn("JDBC::openConnection: no driver available!");
197 protected void closeConnection() {
198 logger.debug("JDBC::closeConnection");
199 // Closes all open connection pools
200 Yank.releaseDefaultConnectionPool();
201 conf.setDbConnected(false);
204 protected boolean checkDBAccessability() {
205 // Check if connection is valid
210 boolean p = pingDB();
212 logger.debug("JDBC::checkDBAcessability, first try connection: {}", p);
213 return (p && !(conf.getErrReconnectThreshold() > 0 && errCnt <= conf.getErrReconnectThreshold()));
217 logger.debug("JDBC::checkDBAcessability, second try connection: {}", p);
218 return (p && !(conf.getErrReconnectThreshold() > 0 && errCnt <= conf.getErrReconnectThreshold()));
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()) {
231 "JDBC::checkDBSchema: Rebuild complete, configure the 'rebuildTableNames' setting to 'false' to stop rebuilds on startup");
234 // Reset the error counter
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);
244 protected String getTable(Item item) {
249 String itemName = item.getName();
250 String tableName = sqlTables.get(itemName);
252 // Table already exists - return the name
253 if (tableName != null) {
257 logger.debug("JDBC::getTable: no table found for item '{}' in sqlTables", itemName);
259 // Create a new entry in items table
260 isvo = new ItemsVO();
261 isvo.setItemname(itemName);
262 isvo = createNewEntryInItemsTable(isvo);
263 rowId = isvo.getItemid();
265 logger.error("JDBC::getTable: Creating table for item '{}' failed.", itemName);
267 // Create the table name
268 logger.debug("JDBC::getTable: getTableName with rowId={} itemName={}", rowId, itemName);
269 tableName = getTableName(rowId, itemName);
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);
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,
284 sqlTables.put(itemName, tableName);
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
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);
300 private void formatTableNames() {
301 boolean tmpinit = initialized;
307 Map<Integer, String> tableIds = new HashMap<>();
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);
318 al = getItemTables();
322 List<ItemVO> oldNewTablenames = new ArrayList<>();
323 for (int i = 0; i < al.size(); i++) {
325 oldName = al.get(i).getTable_name();
326 logger.info("JDBC::formatTableNames: found Table Name= {}", oldName);
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));
336 logger.info("JDBC::formatTableNames: found Table id= {}", id);
338 newName = tableIds.get(id);
339 logger.info("JDBC::formatTableNames: found Table newName= {}", newName);
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);
346 logger.info("JDBC::formatTableNames: Table oldName='{}' newName='{}' nothing to rename", oldName,
350 logger.error("JDBC::formatTableNames: Table '{}' could NOT be renamed to '{}'", oldName, newName);
355 updateItemTableNames(oldNewTablenames);
356 logger.info("JDBC::formatTableNames: Finished updating {} item table names", oldNewTablenames.size());
358 initialized = tmpinit;
361 private String getTableName(int rowId, String itemName) {
362 return getTableNamePrefix(itemName) + formatRight(rowId, conf.getTableIdDigitCount());
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();
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());
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--) {
388 result.append(valueAsString);
389 return result.toString();
391 return valueAsString;
398 private void logTime(String me, long timerStart, long timerStop) {
399 if (conf.enableLogTime && logger.isInfoEnabled()) {
401 int timerDiff = (int) (timerStop - timerStart);
402 if (timerDiff < afterAccessMin) {
403 afterAccessMin = timerDiff;
405 if (timerDiff > afterAccessMax) {
406 afterAccessMax = timerDiff;
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();
414 if (conf.timerCount == 1001) {
415 conf.time1000Statements = Math.round(((int) (System.currentTimeMillis() - conf.timer1000)) / 1000);// Seconds
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);