]> git.basschouten.com Git - openhab-addons.git/blob
bac25c3e5a11ea2bb54fa6c3e7369f185cb49f9c
[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.Date;
17 import java.util.List;
18 import java.util.Locale;
19 import java.util.Map;
20 import java.util.Set;
21
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.openhab.core.config.core.ConfigurableService;
25 import org.openhab.core.i18n.TimeZoneProvider;
26 import org.openhab.core.items.GroupItem;
27 import org.openhab.core.items.Item;
28 import org.openhab.core.items.ItemNotFoundException;
29 import org.openhab.core.items.ItemRegistry;
30 import org.openhab.core.persistence.FilterCriteria;
31 import org.openhab.core.persistence.HistoricItem;
32 import org.openhab.core.persistence.ModifiablePersistenceService;
33 import org.openhab.core.persistence.PersistenceItemInfo;
34 import org.openhab.core.persistence.PersistenceService;
35 import org.openhab.core.persistence.QueryablePersistenceService;
36 import org.openhab.core.persistence.strategy.PersistenceStrategy;
37 import org.openhab.core.types.State;
38 import org.openhab.core.types.UnDefType;
39 import org.osgi.framework.BundleContext;
40 import org.osgi.framework.Constants;
41 import org.osgi.service.component.annotations.Activate;
42 import org.osgi.service.component.annotations.Component;
43 import org.osgi.service.component.annotations.Deactivate;
44 import org.osgi.service.component.annotations.Reference;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47
48 /**
49  * This is the implementation of the JDBC {@link PersistenceService}.
50  *
51  * @author Helmut Lehmeyer - Initial contribution
52  * @author Kai Kreuzer - Migration to 3.x
53  */
54 @NonNullByDefault
55 @Component(service = { PersistenceService.class,
56         QueryablePersistenceService.class }, configurationPid = "org.openhab.jdbc", //
57         property = Constants.SERVICE_PID + "=org.openhab.jdbc")
58 @ConfigurableService(category = "persistence", label = "JDBC Persistence Service", description_uri = JdbcPersistenceService.CONFIG_URI)
59 public class JdbcPersistenceService extends JdbcMapper implements ModifiablePersistenceService {
60
61     private static final String SERVICE_ID = "jdbc";
62     private static final String SERVICE_LABEL = "JDBC";
63     protected static final String CONFIG_URI = "persistence:jdbc";
64
65     private final Logger logger = LoggerFactory.getLogger(JdbcPersistenceService.class);
66
67     private final ItemRegistry itemRegistry;
68
69     @Activate
70     public JdbcPersistenceService(final @Reference ItemRegistry itemRegistry,
71             final @Reference TimeZoneProvider timeZoneProvider) {
72         super(timeZoneProvider);
73         this.itemRegistry = itemRegistry;
74     }
75
76     /**
77      * Called by the SCR to activate the component with its configuration read
78      * from CAS
79      *
80      * @param bundleContext
81      *            BundleContext of the Bundle that defines this component
82      * @param configuration
83      *            Configuration properties for this component obtained from the
84      *            ConfigAdmin service
85      */
86     @Activate
87     public void activate(BundleContext bundleContext, Map<Object, Object> configuration) {
88         logger.debug("JDBC::activate: persistence service activated");
89         updateConfig(configuration);
90     }
91
92     /**
93      * Called by the SCR to deactivate the component when either the
94      * configuration is removed or mandatory references are no longer satisfied
95      * or the component has simply been stopped.
96      *
97      * @param reason
98      *            Reason code for the deactivation:<br>
99      *            <ul>
100      *            <li>0 – Unspecified
101      *            <li>1 – The component was disabled
102      *            <li>2 – A reference became unsatisfied
103      *            <li>3 – A configuration was changed
104      *            <li>4 – A configuration was deleted
105      *            <li>5 – The component was disposed
106      *            <li>6 – The bundle was stopped
107      *            </ul>
108      */
109     @Deactivate
110     public void deactivate(final int reason) {
111         logger.debug("JDBC::deactivate:  persistence bundle stopping. Disconnecting from database. reason={}", reason);
112         // closeConnection();
113         initialized = false;
114     }
115
116     @Override
117     public String getId() {
118         logger.debug("JDBC::getName: returning name 'jdbc' for queryable persistence service.");
119         return SERVICE_ID;
120     }
121
122     @Override
123     public String getLabel(@Nullable Locale locale) {
124         return SERVICE_LABEL;
125     }
126
127     @Override
128     public void store(Item item) {
129         internalStore(item, null, item.getState());
130     }
131
132     @Override
133     public void store(Item item, @Nullable String alias) {
134         // alias is not supported
135         internalStore(item, null, item.getState());
136     }
137
138     @Override
139     public void store(Item item, ZonedDateTime date, State state) {
140         internalStore(item, date, state);
141     }
142
143     private void internalStore(Item item, @Nullable ZonedDateTime date, State state) {
144         // Do not store undefined/uninitialized data
145         if (state instanceof UnDefType) {
146             logger.debug("JDBC::store: ignore Item '{}' because it is UnDefType", item.getName());
147             return;
148         }
149         if (!checkDBAccessability()) {
150             logger.warn(
151                     "JDBC::store: No connection to database. Cannot persist state '{}' for item '{}'! Will retry connecting to database when error count:{} equals errReconnectThreshold:{}",
152                     state, item, errCnt, conf.getErrReconnectThreshold());
153             return;
154         }
155         long timerStart = System.currentTimeMillis();
156         storeItemValue(item, state, date);
157         if (logger.isDebugEnabled()) {
158             logger.debug("JDBC: Stored item '{}' as '{}' in SQL database at {} in {} ms.", item.getName(), state,
159                     new Date(), System.currentTimeMillis() - timerStart);
160         }
161     }
162
163     @Override
164     public Set<PersistenceItemInfo> getItemInfo() {
165         return getItems();
166     }
167
168     /**
169      * Queries the {@link PersistenceService} for data with a given filter
170      * criteria
171      *
172      * @param filter
173      *            the filter to apply to the query
174      * @return a time series of items
175      */
176     @Override
177     public Iterable<HistoricItem> query(FilterCriteria filter) {
178         if (!checkDBAccessability()) {
179             logger.warn("JDBC::query: database not connected, query aborted for item '{}'", filter.getItemName());
180             return List.of();
181         }
182
183         // Get the item name from the filter
184         // Also get the Item object so we can determine the type
185         Item item = null;
186         String itemName = filter.getItemName();
187         logger.debug("JDBC::query: item is {}", itemName);
188         try {
189             item = itemRegistry.getItem(itemName);
190         } catch (ItemNotFoundException e1) {
191             logger.error("JDBC::query: unable to get item for itemName: '{}'. Ignore and give up!", itemName);
192             return List.of();
193         }
194
195         if (item instanceof GroupItem) {
196             // For Group Item is BaseItem needed to get correct Type of Value.
197             item = GroupItem.class.cast(item).getBaseItem();
198             logger.debug("JDBC::query: item is instanceof GroupItem '{}'", itemName);
199             if (item == null) {
200                 logger.debug("JDBC::query: BaseItem of GroupItem is null. Ignore and give up!");
201                 return List.of();
202             }
203             if (item instanceof GroupItem) {
204                 logger.debug("JDBC::query: BaseItem of GroupItem is a GroupItem too. Ignore and give up!");
205                 return List.of();
206             }
207         }
208
209         String table = itemNameToTableNameMap.get(itemName);
210         if (table == null) {
211             logger.debug("JDBC::query: unable to find table for item with name: '{}', no data in database.", itemName);
212             return List.of();
213         }
214
215         long timerStart = System.currentTimeMillis();
216         List<HistoricItem> items = getHistItemFilterQuery(filter, conf.getNumberDecimalcount(), table, item);
217         if (logger.isDebugEnabled()) {
218             logger.debug("JDBC: Query for item '{}' returned {} rows in {} ms", itemName, items.size(),
219                     System.currentTimeMillis() - timerStart);
220         }
221
222         // Success
223         errCnt = 0;
224         return items;
225     }
226
227     public void updateConfig(Map<Object, Object> configuration) {
228         logger.debug("JDBC::updateConfig");
229
230         conf = new JdbcConfiguration(configuration);
231         if (conf.valid && checkDBAccessability()) {
232             namingStrategy = new NamingStrategy(conf);
233             checkDBSchema();
234             // connection has been established ... initialization completed!
235             initialized = true;
236         } else {
237             initialized = false;
238         }
239
240         logger.debug("JDBC::updateConfig: configuration complete for service={}.", getId());
241     }
242
243     @Override
244     public List<PersistenceStrategy> getDefaultStrategies() {
245         return List.of(PersistenceStrategy.Globals.CHANGE);
246     }
247
248     @Override
249     public boolean remove(FilterCriteria filter) throws IllegalArgumentException {
250         if (!checkDBAccessability()) {
251             logger.warn("JDBC::remove: database not connected, remove aborted for item '{}'", filter.getItemName());
252             return false;
253         }
254
255         // Get the item name from the filter
256         // Also get the Item object so we can determine the type
257         String itemName = filter.getItemName();
258         logger.debug("JDBC::remove: item is {}", itemName);
259         if (itemName == null) {
260             throw new IllegalArgumentException("Item name must not be null");
261         }
262
263         String table = itemNameToTableNameMap.get(itemName);
264         if (table == null) {
265             logger.debug("JDBC::remove: unable to find table for item with name: '{}', no data in database.", itemName);
266             return false;
267         }
268
269         long timerStart = System.currentTimeMillis();
270         boolean result = deleteItemValues(filter, table);
271         if (logger.isDebugEnabled()) {
272             logger.debug("JDBC: Deleted values for item '{}' in SQL database at {} in {} ms.", itemName, new Date(),
273                     System.currentTimeMillis() - timerStart);
274         }
275
276         return result;
277     }
278 }