Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[mongodb] Fix quantity type storage, introduce new types #15617

Closed
wants to merge 6 commits into from
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/

package org.openhab.persistence.mongodb.internal;

import java.math.BigDecimal;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Date;
import java.util.TimeZone;

import javax.measure.Unit;

import org.apache.commons.lang3.StringUtils;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please avoid this import. See #7722 for details.

import org.bson.types.ObjectId;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.items.Item;
import org.openhab.core.library.items.ContactItem;
import org.openhab.core.library.items.DateTimeItem;
import org.openhab.core.library.items.DimmerItem;
import org.openhab.core.library.items.ImageItem;
import org.openhab.core.library.items.NumberItem;
import org.openhab.core.library.items.RollershutterItem;
import org.openhab.core.library.items.SwitchItem;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.HSBType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.PointType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.RawType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.types.State;

import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;

import org.eclipse.jdt.annotation.Nullable;

/**
* Conversion logic between openHAB {@link State} types and MongoDB store types
*
* @author Konrad Zawadka - Initial contribution
*/
@NonNullByDefault
public class MongoDBConvertUtils {
@NonNullByDefault
static final Number DIGITAL_VALUE_OFF = 0; // Visible for testing
@NonNullByDefault
static final Number DIGITAL_VALUE_ON = 1; // Visible for testing
@NonNullByDefault
static final String FIELD_ID = "_id";
@NonNullByDefault
static final String FIELD_ITEM = "item";
@NonNullByDefault
static final String FIELD_REALNAME = "realName";
@NonNullByDefault
static final String FIELD_TIMESTAMP = "timestamp";
@NonNullByDefault
static final String FIELD_VALUE = "value";

protected static Object stateToObject(State state) {
Object value;
if (state instanceof HSBType) {
value = state.toString();
} else if (state instanceof PointType) {
value = state.toString();
} else if (state instanceof DecimalType type) {
value = type.toBigDecimal();
} else if (state instanceof QuantityType<?> type) {
value = type.toBigDecimal();
} else if (state instanceof OnOffType) {
value = state == OnOffType.ON ? DIGITAL_VALUE_ON : DIGITAL_VALUE_OFF;
} else if (state instanceof OpenClosedType) {
value = state == OpenClosedType.OPEN ? DIGITAL_VALUE_ON : DIGITAL_VALUE_OFF;
} else if (state instanceof DateTimeType type) {
value = type.getZonedDateTime().toInstant().toEpochMilli();
} else {
value = state.toFullString();
}
return value;
}

protected static DBObject itemToDBObject(Item item, String alias) {
String name = (alias != null) ? alias : item.getName();
Object value = MongoDBConvertUtils.stateToObject(item.getState());

DBObject obj = new BasicDBObject();
obj.put(FIELD_ID, new ObjectId());
obj.put(FIELD_ITEM, name);
obj.put(FIELD_REALNAME, item.getName());
obj.put(FIELD_TIMESTAMP, new Date());
obj.put(FIELD_VALUE, value);
return obj;
}

protected static State objectToState(BasicDBObject obj, Item itemToSetState) {
final State state;
if (itemToSetState instanceof NumberItem numberItem) {
double value = obj.getDouble(FIELD_VALUE);
Unit<?> unit = numberItem.getUnit();
if (unit == null) {
return new DecimalType(value);
} else {
return new QuantityType<>(value, unit);
}
} else if (itemToSetState instanceof DimmerItem) {
return new PercentType(obj.getInt(FIELD_VALUE));
} else if (itemToSetState instanceof SwitchItem) {
return toBoolean(obj.getString(FIELD_VALUE)) ? OnOffType.ON : OnOffType.OFF;
} else if (itemToSetState instanceof ContactItem) {
return toBoolean(obj.getString(FIELD_VALUE)) ? OpenClosedType.OPEN : OpenClosedType.CLOSED;
} else if (itemToSetState instanceof RollershutterItem) {
state = new PercentType(obj.getInt(FIELD_VALUE));
} else if (itemToSetState instanceof DateTimeItem) {
state = getDateTime(obj);
} else if (itemToSetState instanceof ImageItem) {
state = RawType.valueOf(obj.getString(FIELD_VALUE));
} else {
state = new StringType(obj.getString(FIELD_VALUE));
}
return state;
}

private static State getDateTime(BasicDBObject obj) {
String valueStr = obj.getString(FIELD_VALUE);
if (StringUtils.isNumeric(valueStr)) {
Instant i = Instant.ofEpochMilli(new BigDecimal(valueStr).longValue());
ZonedDateTime z = ZonedDateTime.ofInstant(i, TimeZone.getDefault().toZoneId());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TimeZoneProvider should be used to get the time-zone configured in openHAB. You can add a ZoneId parameter and let the service provide this.

See for example:

@Activate
public JdbcPersistenceService(final @Reference ItemRegistry itemRegistry,
final @Reference TimeZoneProvider timeZoneProvider) {
super(timeZoneProvider);
this.itemRegistry = itemRegistry;
}

return new DateTimeType(z);
} else {
return new DateTimeType(
ZonedDateTime.ofInstant(obj.getDate(FIELD_VALUE).toInstant(), ZoneId.systemDefault()));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here. I don't understand why you have chosen a different approach here compared to line 142?

}
}

private static boolean toBoolean(@Nullable Object object) {
if (object instanceof Boolean boolean1) {
return boolean1;
} else if (object != null) {
if ("1".equals(object) || "1.0".equals(object)) {
return true;
} else {
return Boolean.parseBoolean(String.valueOf(object));
}
} else {
return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
*/
package org.openhab.persistence.mongodb.internal;

import static org.openhab.persistence.mongodb.internal.MongoDBConvertUtils.FIELD_ITEM;
import static org.openhab.persistence.mongodb.internal.MongoDBConvertUtils.FIELD_TIMESTAMP;
import static org.openhab.persistence.mongodb.internal.MongoDBConvertUtils.FIELD_VALUE;
import static org.openhab.persistence.mongodb.internal.MongoDBConvertUtils.itemToDBObject;

import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
Expand All @@ -22,24 +27,11 @@
import java.util.Map;
import java.util.Set;

import org.bson.types.ObjectId;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.items.Item;
import org.openhab.core.items.ItemNotFoundException;
import org.openhab.core.items.ItemRegistry;
import org.openhab.core.library.items.ContactItem;
import org.openhab.core.library.items.DateTimeItem;
import org.openhab.core.library.items.DimmerItem;
import org.openhab.core.library.items.NumberItem;
import org.openhab.core.library.items.RollershutterItem;
import org.openhab.core.library.items.SwitchItem;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.persistence.FilterCriteria;
import org.openhab.core.persistence.FilterCriteria.Operator;
import org.openhab.core.persistence.FilterCriteria.Ordering;
Expand Down Expand Up @@ -77,12 +69,6 @@
QueryablePersistenceService.class }, configurationPid = "org.openhab.mongodb", configurationPolicy = ConfigurationPolicy.REQUIRE)
public class MongoDBPersistenceService implements QueryablePersistenceService {

private static final String FIELD_ID = "_id";
private static final String FIELD_ITEM = "item";
private static final String FIELD_REALNAME = "realName";
private static final String FIELD_TIMESTAMP = "timestamp";
private static final String FIELD_VALUE = "value";

private final Logger logger = LoggerFactory.getLogger(MongoDBPersistenceService.class);

private String url = "";
Expand Down Expand Up @@ -172,8 +158,7 @@ public void store(Item item, @Nullable String alias) {
return;
}

String realItemName = item.getName();
String collectionName = collectionPerItem ? realItemName : this.collection;
String collectionName = collectionPerItem ? item.getName() : this.collection;

@Nullable
DBCollection collection = connectToCollection(collectionName);
Expand All @@ -183,32 +168,10 @@ public void store(Item item, @Nullable String alias) {
return;
}

String name = (alias != null) ? alias : realItemName;
Object value = this.convertValue(item.getState());

DBObject obj = new BasicDBObject();
obj.put(FIELD_ID, new ObjectId());
obj.put(FIELD_ITEM, name);
obj.put(FIELD_REALNAME, realItemName);
obj.put(FIELD_TIMESTAMP, new Date());
obj.put(FIELD_VALUE, value);
DBObject obj = itemToDBObject(item, alias);
collection.save(obj);

logger.debug("MongoDB save {}={}", name, value);
}

private Object convertValue(State state) {
Object value;
if (state instanceof PercentType type) {
value = type.toBigDecimal().doubleValue();
} else if (state instanceof DateTimeType type) {
value = Date.from(type.getZonedDateTime().toInstant());
} else if (state instanceof DecimalType type) {
value = type.toBigDecimal().doubleValue();
} else {
value = state.toString();
}
return value;
logger.debug("MongoDB save {}={}", item.getName(), item.getState());
}

@Override
Expand Down Expand Up @@ -369,7 +332,7 @@ public Iterable<HistoricItem> query(FilterCriteria filter) {
return Collections.emptyList();
}

Object value = convertValue(filterState);
Object value = MongoDBConvertUtils.stateToObject(filterState);
query.put(FIELD_VALUE, new BasicDBObject(op, value));
}

Expand All @@ -393,25 +356,7 @@ public Iterable<HistoricItem> query(FilterCriteria filter) {
while (cursor.hasNext()) {
BasicDBObject obj = (BasicDBObject) cursor.next();

final State state;
if (item instanceof NumberItem) {
state = new DecimalType(obj.getDouble(FIELD_VALUE));
} else if (item instanceof DimmerItem) {
state = new PercentType(obj.getInt(FIELD_VALUE));
} else if (item instanceof SwitchItem) {
state = OnOffType.valueOf(obj.getString(FIELD_VALUE));
} else if (item instanceof ContactItem) {
state = OpenClosedType.valueOf(obj.getString(FIELD_VALUE));
} else if (item instanceof RollershutterItem) {
state = new PercentType(obj.getInt(FIELD_VALUE));
} else if (item instanceof DateTimeItem) {
state = new DateTimeType(
ZonedDateTime.ofInstant(obj.getDate(FIELD_VALUE).toInstant(), ZoneId.systemDefault()));
} else {
state = new StringType(obj.getString(FIELD_VALUE));
}

items.add(new MongoDBItem(realItemName, state,
items.add(new MongoDBItem(realItemName, MongoDBConvertUtils.objectToState(obj, item),
ZonedDateTime.ofInstant(obj.getDate(FIELD_TIMESTAMP).toInstant(), ZoneId.systemDefault())));
}

Expand Down
Loading