]> git.basschouten.com Git - openhab-addons.git/blob
eb18986105894f699f11aa7be732d8534a5cdb0e
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 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.Date;
18 import java.util.Map;
19 import java.util.function.BiFunction;
20 import java.util.function.Function;
21
22 import javax.measure.Unit;
23
24 import org.bson.Document;
25 import org.bson.types.Binary;
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.openhab.core.items.GenericItem;
29 import org.openhab.core.items.Item;
30 import org.openhab.core.library.items.CallItem;
31 import org.openhab.core.library.items.ColorItem;
32 import org.openhab.core.library.items.ContactItem;
33 import org.openhab.core.library.items.DateTimeItem;
34 import org.openhab.core.library.items.DimmerItem;
35 import org.openhab.core.library.items.ImageItem;
36 import org.openhab.core.library.items.LocationItem;
37 import org.openhab.core.library.items.NumberItem;
38 import org.openhab.core.library.items.PlayerItem;
39 import org.openhab.core.library.items.RollershutterItem;
40 import org.openhab.core.library.items.StringItem;
41 import org.openhab.core.library.items.SwitchItem;
42 import org.openhab.core.library.types.DateTimeType;
43 import org.openhab.core.library.types.DecimalType;
44 import org.openhab.core.library.types.HSBType;
45 import org.openhab.core.library.types.OnOffType;
46 import org.openhab.core.library.types.OpenClosedType;
47 import org.openhab.core.library.types.PercentType;
48 import org.openhab.core.library.types.PlayPauseType;
49 import org.openhab.core.library.types.PointType;
50 import org.openhab.core.library.types.QuantityType;
51 import org.openhab.core.library.types.RawType;
52 import org.openhab.core.library.types.StringListType;
53 import org.openhab.core.library.types.StringType;
54 import org.openhab.core.persistence.FilterCriteria.Operator;
55 import org.openhab.core.types.State;
56 import org.openhab.core.types.UnDefType;
57 import org.openhab.core.types.util.UnitUtils;
58 import org.slf4j.Logger;
59 import org.slf4j.LoggerFactory;
60
61 /**
62  * This class handles the conversion of types between openHAB and MongoDB.
63  * It provides methods to convert openHAB states to MongoDB compatible types and vice versa.
64  * It also provides a method to convert openHAB filter operators to MongoDB query operators.
65  *
66  * @author RenĂ© Ulbricht - Initial contribution
67  */
68 @NonNullByDefault
69 public class MongoDBTypeConversions {
70
71     /**
72      * Converts a MongoDB document to an openHAB state.
73      *
74      * @param item The openHAB item that the state belongs to.
75      * @param doc The MongoDB document to convert.
76      * @return The openHAB state.
77      * @throws IllegalArgumentException If the item type is not supported.
78      */
79     public static State getStateFromDocument(Item item, Document doc) {
80         BiFunction<Item, Document, State> converter = ITEM_STATE_CONVERTERS.get(item.getClass());
81         if (converter != null) {
82             return converter.apply(item, doc);
83         } else {
84             throw new IllegalArgumentException("Unsupported item type: " + item.getClass().getName());
85         }
86     }
87
88     /**
89      * Converts an openHAB filter operator to a MongoDB query operator.
90      *
91      * @param operator The openHAB filter operator to convert.
92      * @return The MongoDB query operator, or null if the operator is not supported.
93      */
94     public static @Nullable String convertOperator(Operator operator) {
95         return switch (operator) {
96             case EQ -> "$eq";
97             case GT -> "$gt";
98             case GTE -> "$gte";
99             case LT -> "$lt";
100             case LTE -> "$lte";
101             case NEQ -> "$neq";
102             default -> null;
103         };
104     }
105
106     /**
107      * Converts an openHAB state to a MongoDB compatible type.
108      *
109      * @param state The openHAB state to convert.
110      * @return The MongoDB compatible type.
111      */
112     public static Object convertValue(State state) {
113         return STATE_CONVERTERS.getOrDefault(state.getClass(), State::toString).apply(state);
114     }
115
116     private static final Logger logger = LoggerFactory.getLogger(MongoDBTypeConversions.class);
117
118     /**
119      * A map of converters that convert openHAB states to MongoDB compatible types.
120      * Each converter is a function that takes an openHAB state and returns an object that can be stored in MongoDB.
121      */
122     private static final Map<Class<? extends State>, Function<State, Object>> STATE_CONVERTERS = Map.of( //
123             HSBType.class, State::toString, //
124             QuantityType.class, state -> ((QuantityType<?>) state).toBigDecimal().doubleValue(), //
125             PercentType.class, state -> ((PercentType) state).intValue(), //
126             DateTimeType.class, state -> ((DateTimeType) state).getZonedDateTime().toString(), //
127             StringListType.class, State::toString, //
128             DecimalType.class, state -> ((DecimalType) state).toBigDecimal().doubleValue(), //
129             RawType.class, MongoDBTypeConversions::handleRawType//
130     );
131
132     private static Object handleRawType(State state) {
133         RawType rawType = (RawType) state;
134         Document doc = new Document();
135         doc.put(MongoDBFields.FIELD_VALUE_TYPE, rawType.getMimeType());
136         doc.put(MongoDBFields.FIELD_VALUE_DATA, rawType.getBytes());
137         return doc;
138     }
139
140     /**
141      * A map of converters that convert MongoDB documents to openHAB states.
142      * Each converter is a function that takes an openHAB item and a MongoDB document and returns an openHAB state.
143      */
144
145     private static final Map<Class<? extends Item>, BiFunction<Item, Document, State>> ITEM_STATE_CONVERTERS = //
146             Map.ofEntries( //
147                     Map.entry(NumberItem.class, MongoDBTypeConversions::handleNumberItem),
148                     Map.entry(ColorItem.class, MongoDBTypeConversions::handleColorItem),
149                     Map.entry(DimmerItem.class, MongoDBTypeConversions::handleDimmerItem),
150                     Map.entry(SwitchItem.class,
151                             (Item item, Document doc) -> OnOffType.valueOf(doc.getString(MongoDBFields.FIELD_VALUE))),
152                     Map.entry(ContactItem.class,
153                             (Item item, Document doc) -> OpenClosedType
154                                     .valueOf(doc.getString(MongoDBFields.FIELD_VALUE))),
155                     Map.entry(RollershutterItem.class, MongoDBTypeConversions::handleRollershutterItem),
156                     Map.entry(DateTimeItem.class, MongoDBTypeConversions::handleDateTimeItem),
157                     Map.entry(LocationItem.class,
158                             (Item item, Document doc) -> new PointType(doc.getString(MongoDBFields.FIELD_VALUE))),
159                     Map.entry(PlayerItem.class,
160                             (Item item, Document doc) -> PlayPauseType
161                                     .valueOf(doc.getString(MongoDBFields.FIELD_VALUE))),
162                     Map.entry(CallItem.class,
163                             (Item item, Document doc) -> new StringListType(doc.getString(MongoDBFields.FIELD_VALUE))),
164                     Map.entry(ImageItem.class, MongoDBTypeConversions::handleImageItem), //
165                     Map.entry(StringItem.class,
166                             (Item item, Document doc) -> new StringType(doc.getString(MongoDBFields.FIELD_VALUE))),
167                     Map.entry(GenericItem.class,
168                             (Item item, Document doc) -> new StringType(doc.getString(MongoDBFields.FIELD_VALUE)))//
169             );
170
171     private static State handleNumberItem(Item item, Document doc) {
172         NumberItem numberItem = (NumberItem) item;
173         Unit<?> unit = numberItem.getUnit();
174         Object value = doc.get(MongoDBFields.FIELD_VALUE);
175         if (value == null) {
176             return UnDefType.UNDEF;
177         }
178         if (doc.containsKey(MongoDBFields.FIELD_UNIT)) {
179             String unitString = doc.getString(MongoDBFields.FIELD_UNIT);
180             Unit<?> docUnit = UnitUtils.parseUnit(unitString);
181             if (docUnit != null) {
182                 unit = docUnit;
183             }
184         }
185         if (value instanceof String) {
186             return new QuantityType<>(value.toString());
187         }
188         if (unit != null) {
189             return new QuantityType<>(((Number) value).doubleValue(), unit);
190         } else {
191             return new DecimalType(((Number) value).doubleValue());
192         }
193     }
194
195     private static State handleColorItem(Item item, Document doc) {
196         Object value = doc.get(MongoDBFields.FIELD_VALUE);
197         if (value instanceof String) {
198             return new HSBType(value.toString());
199         } else {
200             logger.warn("HSBType ({}) value is not a valid string: {}", doc.getString(MongoDBFields.FIELD_REALNAME),
201                     value);
202             return new HSBType("0,0,0");
203         }
204     }
205
206     private static State handleDimmerItem(Item item, Document doc) {
207         Object value = doc.get(MongoDBFields.FIELD_VALUE);
208         if (value == null) {
209             return UnDefType.UNDEF;
210         }
211         if (value instanceof Integer) {
212             return new PercentType((Integer) value);
213         } else {
214             return new PercentType(((Number) value).intValue());
215         }
216     }
217
218     private static State handleRollershutterItem(Item item, Document doc) {
219         Object value = doc.get(MongoDBFields.FIELD_VALUE);
220         if (value == null) {
221             return UnDefType.UNDEF;
222         }
223         if (value instanceof Integer) {
224             return new PercentType((Integer) value);
225         } else {
226             return new PercentType(((Number) value).intValue());
227         }
228     }
229
230     private static State handleDateTimeItem(Item item, Document doc) {
231         Object value = doc.get(MongoDBFields.FIELD_VALUE);
232         if (value == null) {
233             return UnDefType.UNDEF;
234         }
235         if (value instanceof String) {
236             return new DateTimeType(ZonedDateTime.parse(doc.getString(MongoDBFields.FIELD_VALUE)));
237         } else {
238             return new DateTimeType(ZonedDateTime.ofInstant(((Date) value).toInstant(), ZoneId.systemDefault()));
239         }
240     }
241
242     private static State handleImageItem(Item item, Document doc) {
243         Object value = doc.get(MongoDBFields.FIELD_VALUE);
244         if (value instanceof Document) {
245             Document fieldValue = (Document) value;
246             String type = fieldValue.getString(MongoDBFields.FIELD_VALUE_TYPE);
247             Binary data = fieldValue.get(MongoDBFields.FIELD_VALUE_DATA, Binary.class);
248             return new RawType(data.getData(), type);
249         } else {
250             logger.warn("ImageItem ({}) value is not a Document: {}", doc.getString(MongoDBFields.FIELD_REALNAME),
251                     value);
252             return new RawType(new byte[0], "application/octet-stream");
253         }
254     }
255 }