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.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;
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.
66 * @author René Ulbricht - Initial contribution
69 public class MongoDBTypeConversions {
72 * Converts a MongoDB document to an openHAB state.
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.
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);
84 throw new IllegalArgumentException("Unsupported item type: " + item.getClass().getName());
89 * Converts an openHAB filter operator to a MongoDB query operator.
91 * @param operator The openHAB filter operator to convert.
92 * @return The MongoDB query operator, or null if the operator is not supported.
94 public static @Nullable String convertOperator(Operator operator) {
95 return switch (operator) {
107 * Converts an openHAB state to a MongoDB compatible type.
109 * @param state The openHAB state to convert.
110 * @return The MongoDB compatible type.
112 public static Object convertValue(State state) {
113 return STATE_CONVERTERS.getOrDefault(state.getClass(), State::toString).apply(state);
116 private static final Logger logger = LoggerFactory.getLogger(MongoDBTypeConversions.class);
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.
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//
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());
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.
145 private static final Map<Class<? extends Item>, BiFunction<Item, Document, State>> ITEM_STATE_CONVERTERS = //
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)))//
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);
176 return UnDefType.UNDEF;
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) {
185 if (value instanceof String) {
186 return new QuantityType<>(value.toString());
189 return new QuantityType<>(((Number) value).doubleValue(), unit);
191 return new DecimalType(((Number) value).doubleValue());
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());
200 logger.warn("HSBType ({}) value is not a valid string: {}", doc.getString(MongoDBFields.FIELD_REALNAME),
202 return new HSBType("0,0,0");
206 private static State handleDimmerItem(Item item, Document doc) {
207 Object value = doc.get(MongoDBFields.FIELD_VALUE);
209 return UnDefType.UNDEF;
211 if (value instanceof Integer) {
212 return new PercentType((Integer) value);
214 return new PercentType(((Number) value).intValue());
218 private static State handleRollershutterItem(Item item, Document doc) {
219 Object value = doc.get(MongoDBFields.FIELD_VALUE);
221 return UnDefType.UNDEF;
223 if (value instanceof Integer) {
224 return new PercentType((Integer) value);
226 return new PercentType(((Number) value).intValue());
230 private static State handleDateTimeItem(Item item, Document doc) {
231 Object value = doc.get(MongoDBFields.FIELD_VALUE);
233 return UnDefType.UNDEF;
235 if (value instanceof String) {
236 return new DateTimeType(ZonedDateTime.parse(doc.getString(MongoDBFields.FIELD_VALUE)));
238 return new DateTimeType(ZonedDateTime.ofInstant(((Date) value).toInstant(), ZoneId.systemDefault()));
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);
250 logger.warn("ImageItem ({}) value is not a Document: {}", doc.getString(MongoDBFields.FIELD_REALNAME),
252 return new RawType(new byte[0], "application/octet-stream");