2 * Copyright (c) 2010-2024 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.mongodb.internal;
15 import java.time.ZoneId;
16 import java.time.ZonedDateTime;
17 import java.util.Date;
19 import java.util.function.BiFunction;
20 import java.util.function.Function;
22 import javax.measure.Unit;
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.GroupItem;
30 import org.openhab.core.items.Item;
31 import org.openhab.core.library.items.CallItem;
32 import org.openhab.core.library.items.ColorItem;
33 import org.openhab.core.library.items.ContactItem;
34 import org.openhab.core.library.items.DateTimeItem;
35 import org.openhab.core.library.items.DimmerItem;
36 import org.openhab.core.library.items.ImageItem;
37 import org.openhab.core.library.items.LocationItem;
38 import org.openhab.core.library.items.NumberItem;
39 import org.openhab.core.library.items.PlayerItem;
40 import org.openhab.core.library.items.RollershutterItem;
41 import org.openhab.core.library.items.StringItem;
42 import org.openhab.core.library.items.SwitchItem;
43 import org.openhab.core.library.types.DateTimeType;
44 import org.openhab.core.library.types.DecimalType;
45 import org.openhab.core.library.types.HSBType;
46 import org.openhab.core.library.types.OnOffType;
47 import org.openhab.core.library.types.OpenClosedType;
48 import org.openhab.core.library.types.PercentType;
49 import org.openhab.core.library.types.PlayPauseType;
50 import org.openhab.core.library.types.PointType;
51 import org.openhab.core.library.types.QuantityType;
52 import org.openhab.core.library.types.RawType;
53 import org.openhab.core.library.types.StringListType;
54 import org.openhab.core.library.types.StringType;
55 import org.openhab.core.persistence.FilterCriteria.Operator;
56 import org.openhab.core.types.State;
57 import org.openhab.core.types.UnDefType;
58 import org.openhab.core.types.util.UnitUtils;
59 import org.slf4j.Logger;
60 import org.slf4j.LoggerFactory;
63 * This class handles the conversion of types between openHAB and MongoDB.
64 * It provides methods to convert openHAB states to MongoDB compatible types and vice versa.
65 * It also provides a method to convert openHAB filter operators to MongoDB query operators.
67 * @author René Ulbricht - Initial contribution
70 public class MongoDBTypeConversions {
73 * Converts a MongoDB document to an openHAB state.
75 * @param item The openHAB item that the state belongs to.
76 * @param doc The MongoDB document to convert.
77 * @return The openHAB state.
78 * @throws IllegalArgumentException If the item type is not supported.
80 public static State getStateFromDocument(Item item, Document doc) {
81 Item realItem = item instanceof GroupItem groupItem ? groupItem.getBaseItem() : item;
82 BiFunction<Item, Document, State> converter = ITEM_STATE_CONVERTERS.get(realItem.getClass());
83 if (converter != null) {
84 return converter.apply(realItem, doc);
86 throw new IllegalArgumentException("Unsupported item type: " + realItem.getClass().getName());
91 * Converts an openHAB filter operator to a MongoDB query operator.
93 * @param operator The openHAB filter operator to convert.
94 * @return The MongoDB query operator, or null if the operator is not supported.
96 public static @Nullable String convertOperator(Operator operator) {
97 return switch (operator) {
109 * Converts an openHAB state to a MongoDB compatible type.
111 * @param state The openHAB state to convert.
112 * @return The MongoDB compatible type.
114 public static Object convertValue(State state) {
115 return STATE_CONVERTERS.getOrDefault(state.getClass(), State::toString).apply(state);
118 private static final Logger logger = LoggerFactory.getLogger(MongoDBTypeConversions.class);
121 * A map of converters that convert openHAB states to MongoDB compatible types.
122 * Each converter is a function that takes an openHAB state and returns an object that can be stored in MongoDB.
124 private static final Map<Class<? extends State>, Function<State, Object>> STATE_CONVERTERS = Map.of( //
125 HSBType.class, State::toString, //
126 QuantityType.class, state -> ((QuantityType<?>) state).toBigDecimal().doubleValue(), //
127 PercentType.class, state -> ((PercentType) state).intValue(), //
128 DateTimeType.class, state -> ((DateTimeType) state).getZonedDateTime().toString(), //
129 StringListType.class, State::toString, //
130 DecimalType.class, state -> ((DecimalType) state).toBigDecimal().doubleValue(), //
131 RawType.class, MongoDBTypeConversions::handleRawType//
134 private static Object handleRawType(State state) {
135 RawType rawType = (RawType) state;
136 Document doc = new Document();
137 doc.put(MongoDBFields.FIELD_VALUE_TYPE, rawType.getMimeType());
138 doc.put(MongoDBFields.FIELD_VALUE_DATA, rawType.getBytes());
143 * A map of converters that convert MongoDB documents to openHAB states.
144 * Each converter is a function that takes an openHAB item and a MongoDB document and returns an openHAB state.
147 private static final Map<Class<? extends Item>, BiFunction<Item, Document, State>> ITEM_STATE_CONVERTERS = //
149 Map.entry(NumberItem.class, MongoDBTypeConversions::handleNumberItem),
150 Map.entry(ColorItem.class, MongoDBTypeConversions::handleColorItem),
151 Map.entry(DimmerItem.class, MongoDBTypeConversions::handleDimmerItem),
152 Map.entry(SwitchItem.class,
153 (Item item, Document doc) -> OnOffType.valueOf(doc.getString(MongoDBFields.FIELD_VALUE))),
154 Map.entry(ContactItem.class,
155 (Item item, Document doc) -> OpenClosedType
156 .valueOf(doc.getString(MongoDBFields.FIELD_VALUE))),
157 Map.entry(RollershutterItem.class, MongoDBTypeConversions::handleRollershutterItem),
158 Map.entry(DateTimeItem.class, MongoDBTypeConversions::handleDateTimeItem),
159 Map.entry(LocationItem.class,
160 (Item item, Document doc) -> new PointType(doc.getString(MongoDBFields.FIELD_VALUE))),
161 Map.entry(PlayerItem.class,
162 (Item item, Document doc) -> PlayPauseType
163 .valueOf(doc.getString(MongoDBFields.FIELD_VALUE))),
164 Map.entry(CallItem.class,
165 (Item item, Document doc) -> new StringListType(doc.getString(MongoDBFields.FIELD_VALUE))),
166 Map.entry(ImageItem.class, MongoDBTypeConversions::handleImageItem), //
167 Map.entry(StringItem.class,
168 (Item item, Document doc) -> new StringType(doc.getString(MongoDBFields.FIELD_VALUE))),
169 Map.entry(GenericItem.class,
170 (Item item, Document doc) -> new StringType(doc.getString(MongoDBFields.FIELD_VALUE)))//
173 private static State handleNumberItem(Item item, Document doc) {
174 NumberItem numberItem = (NumberItem) item;
175 Unit<?> unit = numberItem.getUnit();
176 Object value = doc.get(MongoDBFields.FIELD_VALUE);
178 return UnDefType.UNDEF;
180 if (doc.containsKey(MongoDBFields.FIELD_UNIT)) {
181 String unitString = doc.getString(MongoDBFields.FIELD_UNIT);
182 Unit<?> docUnit = UnitUtils.parseUnit(unitString);
183 if (docUnit != null) {
187 if (value instanceof String) {
188 return new QuantityType<>(value.toString());
191 return new QuantityType<>(((Number) value).doubleValue(), unit);
193 return new DecimalType(((Number) value).doubleValue());
197 private static State handleColorItem(Item item, Document doc) {
198 Object value = doc.get(MongoDBFields.FIELD_VALUE);
199 if (value instanceof String) {
200 return new HSBType(value.toString());
202 logger.warn("HSBType ({}) value is not a valid string: {}", doc.getString(MongoDBFields.FIELD_REALNAME),
204 return new HSBType("0,0,0");
208 private static State handleDimmerItem(Item item, Document doc) {
209 Object value = doc.get(MongoDBFields.FIELD_VALUE);
211 return UnDefType.UNDEF;
213 if (value instanceof Integer) {
214 return new PercentType((Integer) value);
216 return new PercentType(((Number) value).intValue());
220 private static State handleRollershutterItem(Item item, Document doc) {
221 Object value = doc.get(MongoDBFields.FIELD_VALUE);
223 return UnDefType.UNDEF;
225 if (value instanceof Integer) {
226 return new PercentType((Integer) value);
228 return new PercentType(((Number) value).intValue());
232 private static State handleDateTimeItem(Item item, Document doc) {
233 Object value = doc.get(MongoDBFields.FIELD_VALUE);
235 return UnDefType.UNDEF;
237 if (value instanceof String) {
238 return new DateTimeType(ZonedDateTime.parse(doc.getString(MongoDBFields.FIELD_VALUE)));
240 return new DateTimeType(ZonedDateTime.ofInstant(((Date) value).toInstant(), ZoneId.systemDefault()));
244 private static State handleImageItem(Item item, Document doc) {
245 Object value = doc.get(MongoDBFields.FIELD_VALUE);
246 if (value instanceof Document) {
247 Document fieldValue = (Document) value;
248 String type = fieldValue.getString(MongoDBFields.FIELD_VALUE_TYPE);
249 Binary data = fieldValue.get(MongoDBFields.FIELD_VALUE_DATA, Binary.class);
250 return new RawType(data.getData(), type);
252 logger.warn("ImageItem ({}) value is not a Document: {}", doc.getString(MongoDBFields.FIELD_REALNAME),
254 return new RawType(new byte[0], "application/octet-stream");