]> git.basschouten.com Git - openhab-addons.git/blob
f6b0ced12409a6c6faae35a621416d0dd6bc2cef
[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.mongodb.internal;
14
15 import java.time.ZoneId;
16 import java.time.ZonedDateTime;
17 import java.util.ArrayList;
18 import java.util.Collections;
19 import java.util.Date;
20 import java.util.List;
21 import java.util.Locale;
22 import java.util.Map;
23 import java.util.Set;
24
25 import org.bson.types.ObjectId;
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.openhab.core.items.Item;
29 import org.openhab.core.items.ItemNotFoundException;
30 import org.openhab.core.items.ItemRegistry;
31 import org.openhab.core.library.items.ContactItem;
32 import org.openhab.core.library.items.DateTimeItem;
33 import org.openhab.core.library.items.DimmerItem;
34 import org.openhab.core.library.items.NumberItem;
35 import org.openhab.core.library.items.RollershutterItem;
36 import org.openhab.core.library.items.SwitchItem;
37 import org.openhab.core.library.types.DateTimeType;
38 import org.openhab.core.library.types.DecimalType;
39 import org.openhab.core.library.types.OnOffType;
40 import org.openhab.core.library.types.OpenClosedType;
41 import org.openhab.core.library.types.PercentType;
42 import org.openhab.core.library.types.StringType;
43 import org.openhab.core.persistence.FilterCriteria;
44 import org.openhab.core.persistence.FilterCriteria.Operator;
45 import org.openhab.core.persistence.FilterCriteria.Ordering;
46 import org.openhab.core.persistence.HistoricItem;
47 import org.openhab.core.persistence.PersistenceItemInfo;
48 import org.openhab.core.persistence.PersistenceService;
49 import org.openhab.core.persistence.QueryablePersistenceService;
50 import org.openhab.core.persistence.strategy.PersistenceStrategy;
51 import org.openhab.core.types.State;
52 import org.openhab.core.types.UnDefType;
53 import org.osgi.framework.BundleContext;
54 import org.osgi.service.component.annotations.Activate;
55 import org.osgi.service.component.annotations.Component;
56 import org.osgi.service.component.annotations.ConfigurationPolicy;
57 import org.osgi.service.component.annotations.Deactivate;
58 import org.osgi.service.component.annotations.Reference;
59 import org.slf4j.Logger;
60 import org.slf4j.LoggerFactory;
61
62 import com.mongodb.BasicDBObject;
63 import com.mongodb.DBCollection;
64 import com.mongodb.DBCursor;
65 import com.mongodb.DBObject;
66 import com.mongodb.MongoClient;
67 import com.mongodb.MongoClientURI;
68
69 /**
70  * This is the implementation of the MongoDB {@link PersistenceService}.
71  *
72  * @author Thorsten Hoeger - Initial contribution
73  */
74 @NonNullByDefault
75 @Component(service = { PersistenceService.class,
76         QueryablePersistenceService.class }, configurationPid = "org.openhab.mongodb", configurationPolicy = ConfigurationPolicy.REQUIRE)
77 public class MongoDBPersistenceService implements QueryablePersistenceService {
78
79     private static final String FIELD_ID = "_id";
80     private static final String FIELD_ITEM = "item";
81     private static final String FIELD_REALNAME = "realName";
82     private static final String FIELD_TIMESTAMP = "timestamp";
83     private static final String FIELD_VALUE = "value";
84
85     private final Logger logger = LoggerFactory.getLogger(MongoDBPersistenceService.class);
86
87     private @NonNullByDefault({}) String url;
88     private @NonNullByDefault({}) String db;
89     private @NonNullByDefault({}) String collection;
90
91     private boolean initialized = false;
92
93     protected final ItemRegistry itemRegistry;
94
95     private @NonNullByDefault({}) MongoClient cl;
96     private @NonNullByDefault({}) DBCollection mongoCollection;
97
98     @Activate
99     public MongoDBPersistenceService(final @Reference ItemRegistry itemRegistry) {
100         this.itemRegistry = itemRegistry;
101     }
102
103     @Activate
104     public void activate(final BundleContext bundleContext, final Map<String, Object> config) {
105         url = (String) config.get("url");
106         logger.debug("MongoDB URL {}", url);
107         if (url == null || url.isBlank()) {
108             logger.warn("The MongoDB database URL is missing - please configure the mongodb:url parameter.");
109             return;
110         }
111         db = (String) config.get("database");
112         logger.debug("MongoDB database {}", db);
113         if (db == null || db.isBlank()) {
114             logger.warn("The MongoDB database name is missing - please configure the mongodb:database parameter.");
115             return;
116         }
117         collection = (String) config.get("collection");
118         logger.debug("MongoDB collection {}", collection);
119         if (collection == null || collection.isBlank()) {
120             logger.warn(
121                     "The MongoDB database collection is missing - please configure the mongodb:collection parameter.");
122             return;
123         }
124
125         disconnectFromDatabase();
126         connectToDatabase();
127
128         // connection has been established... initialization completed!
129         initialized = true;
130     }
131
132     @Deactivate
133     public void deactivate(final int reason) {
134         logger.debug("MongoDB persistence bundle stopping. Disconnecting from database.");
135         disconnectFromDatabase();
136     }
137
138     @Override
139     public String getId() {
140         return "mongodb";
141     }
142
143     @Override
144     public String getLabel(@Nullable Locale locale) {
145         return "Mongo DB";
146     }
147
148     @Override
149     public void store(Item item, @Nullable String alias) {
150         // Don't log undefined/uninitialized data
151         if (item.getState() instanceof UnDefType) {
152             return;
153         }
154
155         // If we've not initialized the bundle, then return
156         if (!initialized) {
157             logger.warn("MongoDB not initialized");
158             return;
159         }
160
161         // Connect to mongodb server if we're not already connected
162         if (!isConnected()) {
163             connectToDatabase();
164         }
165
166         // If we still didn't manage to connect, then return!
167         if (!isConnected()) {
168             logger.warn(
169                     "mongodb: No connection to database. Cannot persist item '{}'! Will retry connecting to database next time.",
170                     item);
171             return;
172         }
173
174         String realName = item.getName();
175         String name = (alias != null) ? alias : realName;
176         Object value = this.convertValue(item.getState());
177
178         DBObject obj = new BasicDBObject();
179         obj.put(FIELD_ID, new ObjectId());
180         obj.put(FIELD_ITEM, name);
181         obj.put(FIELD_REALNAME, realName);
182         obj.put(FIELD_TIMESTAMP, new Date());
183         obj.put(FIELD_VALUE, value);
184         this.mongoCollection.save(obj);
185
186         logger.debug("MongoDB save {}={}", name, value);
187     }
188
189     private Object convertValue(State state) {
190         Object value;
191         if (state instanceof PercentType) {
192             value = ((PercentType) state).toBigDecimal().doubleValue();
193         } else if (state instanceof DateTimeType) {
194             value = Date.from(((DateTimeType) state).getZonedDateTime().toInstant());
195         } else if (state instanceof DecimalType) {
196             value = ((DecimalType) state).toBigDecimal().doubleValue();
197         } else {
198             value = state.toString();
199         }
200         return value;
201     }
202
203     /**
204      * @{inheritDoc
205      */
206     @Override
207     public void store(Item item) {
208         store(item, null);
209     }
210
211     @Override
212     public Set<PersistenceItemInfo> getItemInfo() {
213         return Collections.emptySet();
214     }
215
216     /**
217      * Checks if we have a database connection
218      *
219      * @return true if connection has been established, false otherwise
220      */
221     private boolean isConnected() {
222         return cl != null;
223     }
224
225     /**
226      * Connects to the database
227      */
228     private void connectToDatabase() {
229         try {
230             logger.debug("Connect MongoDB");
231             this.cl = new MongoClient(new MongoClientURI(this.url));
232             mongoCollection = cl.getDB(this.db).getCollection(this.collection);
233
234             BasicDBObject idx = new BasicDBObject();
235             idx.append(FIELD_TIMESTAMP, 1).append(FIELD_ITEM, 1);
236             this.mongoCollection.createIndex(idx);
237             logger.debug("Connect MongoDB ... done");
238         } catch (Exception e) {
239             logger.error("Failed to connect to database {}", this.url);
240             throw new RuntimeException("Cannot connect to database", e);
241         }
242     }
243
244     /**
245      * Disconnects from the database
246      */
247     private void disconnectFromDatabase() {
248         this.mongoCollection = null;
249         if (this.cl != null) {
250             this.cl.close();
251         }
252         cl = null;
253     }
254
255     @Override
256     public Iterable<HistoricItem> query(FilterCriteria filter) {
257         if (!initialized) {
258             return Collections.emptyList();
259         }
260
261         if (!isConnected()) {
262             connectToDatabase();
263         }
264
265         if (!isConnected()) {
266             return Collections.emptyList();
267         }
268
269         String name = filter.getItemName();
270         Item item = getItem(name);
271
272         List<HistoricItem> items = new ArrayList<>();
273         DBObject query = new BasicDBObject();
274         if (filter.getItemName() != null) {
275             query.put(FIELD_ITEM, filter.getItemName());
276         }
277         if (filter.getState() != null && filter.getOperator() != null) {
278             String op = convertOperator(filter.getOperator());
279             Object value = convertValue(filter.getState());
280             query.put(FIELD_VALUE, new BasicDBObject(op, value));
281         }
282         if (filter.getBeginDate() != null) {
283             query.put(FIELD_TIMESTAMP, new BasicDBObject("$gte", filter.getBeginDate()));
284         }
285         if (filter.getBeginDate() != null) {
286             query.put(FIELD_TIMESTAMP, new BasicDBObject("$lte", filter.getBeginDate()));
287         }
288
289         Integer sortDir = (filter.getOrdering() == Ordering.ASCENDING) ? 1 : -1;
290         DBCursor cursor = this.mongoCollection.find(query).sort(new BasicDBObject(FIELD_TIMESTAMP, sortDir))
291                 .skip(filter.getPageNumber() * filter.getPageSize()).limit(filter.getPageSize());
292
293         while (cursor.hasNext()) {
294             BasicDBObject obj = (BasicDBObject) cursor.next();
295
296             final State state;
297             if (item instanceof NumberItem) {
298                 state = new DecimalType(obj.getDouble(FIELD_VALUE));
299             } else if (item instanceof DimmerItem) {
300                 state = new PercentType(obj.getInt(FIELD_VALUE));
301             } else if (item instanceof SwitchItem) {
302                 state = OnOffType.valueOf(obj.getString(FIELD_VALUE));
303             } else if (item instanceof ContactItem) {
304                 state = OpenClosedType.valueOf(obj.getString(FIELD_VALUE));
305             } else if (item instanceof RollershutterItem) {
306                 state = new PercentType(obj.getInt(FIELD_VALUE));
307             } else if (item instanceof DateTimeItem) {
308                 state = new DateTimeType(
309                         ZonedDateTime.ofInstant(obj.getDate(FIELD_VALUE).toInstant(), ZoneId.systemDefault()));
310             } else {
311                 state = new StringType(obj.getString(FIELD_VALUE));
312             }
313
314             items.add(new MongoDBItem(name, state,
315                     ZonedDateTime.ofInstant(obj.getDate(FIELD_TIMESTAMP).toInstant(), ZoneId.systemDefault())));
316         }
317
318         return items;
319     }
320
321     private @Nullable String convertOperator(Operator operator) {
322         switch (operator) {
323             case EQ:
324                 return "$eq";
325             case GT:
326                 return "$gt";
327             case GTE:
328                 return "$gte";
329             case LT:
330                 return "$lt";
331             case LTE:
332                 return "$lte";
333             case NEQ:
334                 return "$neq";
335             default:
336                 return null;
337         }
338     }
339
340     private @Nullable Item getItem(String itemName) {
341         try {
342             return itemRegistry.getItem(itemName);
343         } catch (ItemNotFoundException e1) {
344             logger.error("Unable to get item type for {}", itemName);
345         }
346         return null;
347     }
348
349     @Override
350     public List<PersistenceStrategy> getDefaultStrategies() {
351         return Collections.emptyList();
352     }
353 }