mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-26 15:21:41 +01:00
[mongodb] Upgrade DB driver, add more type handlings, fix QuantityType handling (#16333)
* #16308 #16310 Upgraded MongoDB driver, added initial unit tests * #16308 #16310 Refactored the MongoDBPersistence adding helper, fixing type handling for HSBType, RawType and QuantityType * #16308 Added backwardcompatibility for the old way of writting the data where possible * #16308 Added test for larger ImageItems and the limit of 16 MB Signed-off-by: René Ulbricht <rene_ulbricht@outlook.com> Signed-off-by: Ciprian Pascu <contact@ciprianpascu.ro>
This commit is contained in:
parent
288b40fb90
commit
e51633b225
@ -14,12 +14,66 @@
|
|||||||
|
|
||||||
<name>openHAB Add-ons :: Bundles :: Persistence Service :: MongoDB</name>
|
<name>openHAB Add-ons :: Bundles :: Persistence Service :: MongoDB</name>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<bnd.importpackage>!sun.nio.ch;!org.bson.codecs.kotlin*;!jnr.unixsocket*;!javax.annotation*;!com.google*;!io.netty*;com.oracle*;resolution:=optional;com.aayushatharva*;resolution:=optional;com.mongodb.crypt*;resolution:=optional;com.amazon*;resolution:=optional;software.amazon*;resolution:=optional</bnd.importpackage>
|
||||||
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<!-- https://mvnrepository.com/artifact/org.mongodb/mongo-java-driver -->
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.mongodb</groupId>
|
<groupId>org.mongodb</groupId>
|
||||||
<artifactId>mongo-java-driver</artifactId>
|
<artifactId>mongodb-driver-sync</artifactId>
|
||||||
<version>2.13.1</version>
|
<version>4.11.1</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mongodb</groupId>
|
||||||
|
<artifactId>bson</artifactId>
|
||||||
|
<version>4.11.1</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mongodb</groupId>
|
||||||
|
<artifactId>mongodb-driver-core</artifactId>
|
||||||
|
<version>4.11.1</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.xerial.snappy</groupId>
|
||||||
|
<artifactId>snappy-java</artifactId>
|
||||||
|
<version>1.1.10.3</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.luben</groupId>
|
||||||
|
<artifactId>zstd-jni</artifactId>
|
||||||
|
<version>1.5.5-3</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mongodb</groupId>
|
||||||
|
<artifactId>bson-record-codec</artifactId>
|
||||||
|
<version>4.11.1</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Test dependencies -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>de.bwaldvogel</groupId>
|
||||||
|
<artifactId>mongo-java-server</artifactId>
|
||||||
|
<version>1.44.0</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.testcontainers</groupId>
|
||||||
|
<artifactId>mongodb</artifactId>
|
||||||
|
<version>1.19.4</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-lang3</artifactId>
|
||||||
|
<version>3.14.0</version>
|
||||||
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2024 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 org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class defines constant field names used in MongoDB documents.
|
||||||
|
* These field names are used to ensure consistent access to document properties.
|
||||||
|
*
|
||||||
|
* @author René Ulbricht - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public final class MongoDBFields {
|
||||||
|
public static final String FIELD_ID = "_id";
|
||||||
|
public static final String FIELD_ITEM = "item";
|
||||||
|
public static final String FIELD_REALNAME = "realName";
|
||||||
|
public static final String FIELD_TIMESTAMP = "timestamp";
|
||||||
|
public static final String FIELD_VALUE = "value";
|
||||||
|
public static final String FIELD_UNIT = "unit";
|
||||||
|
public static final String FIELD_VALUE_DATA = "value.data";
|
||||||
|
public static final String FIELD_VALUE_TYPE = "value.type";
|
||||||
|
|
||||||
|
private MongoDBFields() {
|
||||||
|
// Private constructor to prevent instantiation
|
||||||
|
}
|
||||||
|
}
|
@ -14,6 +14,7 @@ package org.openhab.persistence.mongodb.internal;
|
|||||||
|
|
||||||
import java.text.DateFormat;
|
import java.text.DateFormat;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.openhab.core.persistence.HistoricItem;
|
import org.openhab.core.persistence.HistoricItem;
|
||||||
@ -54,6 +55,7 @@ public class MongoDBItem implements HistoricItem {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return DateFormat.getDateTimeInstance().format(timestamp) + ": " + name + " -> " + state.toString();
|
Date date = Date.from(timestamp.toInstant());
|
||||||
|
return DateFormat.getDateTimeInstance().format(date) + ": " + name + " -> " + state.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,28 +22,19 @@ import java.util.Locale;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.bson.Document;
|
||||||
import org.bson.types.ObjectId;
|
import org.bson.types.ObjectId;
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.openhab.core.items.Item;
|
import org.openhab.core.items.Item;
|
||||||
import org.openhab.core.items.ItemNotFoundException;
|
import org.openhab.core.items.ItemNotFoundException;
|
||||||
import org.openhab.core.items.ItemRegistry;
|
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.NumberItem;
|
||||||
import org.openhab.core.library.items.RollershutterItem;
|
import org.openhab.core.library.types.QuantityType;
|
||||||
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;
|
||||||
import org.openhab.core.persistence.FilterCriteria.Operator;
|
|
||||||
import org.openhab.core.persistence.FilterCriteria.Ordering;
|
import org.openhab.core.persistence.FilterCriteria.Ordering;
|
||||||
import org.openhab.core.persistence.HistoricItem;
|
import org.openhab.core.persistence.HistoricItem;
|
||||||
|
import org.openhab.core.persistence.ModifiablePersistenceService;
|
||||||
import org.openhab.core.persistence.PersistenceItemInfo;
|
import org.openhab.core.persistence.PersistenceItemInfo;
|
||||||
import org.openhab.core.persistence.PersistenceService;
|
import org.openhab.core.persistence.PersistenceService;
|
||||||
import org.openhab.core.persistence.QueryablePersistenceService;
|
import org.openhab.core.persistence.QueryablePersistenceService;
|
||||||
@ -59,29 +50,23 @@ import org.osgi.service.component.annotations.Reference;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.mongodb.BasicDBObject;
|
import com.mongodb.client.MongoClient;
|
||||||
import com.mongodb.DBCollection;
|
import com.mongodb.client.MongoClients;
|
||||||
import com.mongodb.DBCursor;
|
import com.mongodb.client.MongoCollection;
|
||||||
import com.mongodb.DBObject;
|
import com.mongodb.client.MongoCursor;
|
||||||
import com.mongodb.MongoClient;
|
import com.mongodb.client.result.DeleteResult;
|
||||||
import com.mongodb.MongoClientURI;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the implementation of the MongoDB {@link PersistenceService}.
|
* This is the implementation of the MongoDB {@link PersistenceService}.
|
||||||
*
|
*
|
||||||
* @author Thorsten Hoeger - Initial contribution
|
* @author Thorsten Hoeger - Initial contribution
|
||||||
* @author Stephan Brunner - Query fixes, Cleanup
|
* @author Stephan Brunner - Query fixes, Cleanup
|
||||||
|
* @author René Ulbricht - Fixes type handling, driver update and cleanup
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
@Component(service = { PersistenceService.class,
|
@Component(service = { PersistenceService.class, QueryablePersistenceService.class,
|
||||||
QueryablePersistenceService.class }, configurationPid = "org.openhab.mongodb", configurationPolicy = ConfigurationPolicy.REQUIRE)
|
ModifiablePersistenceService.class }, configurationPid = "org.openhab.mongodb", configurationPolicy = ConfigurationPolicy.REQUIRE)
|
||||||
public class MongoDBPersistenceService implements QueryablePersistenceService {
|
public class MongoDBPersistenceService implements ModifiablePersistenceService {
|
||||||
|
|
||||||
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 final Logger logger = LoggerFactory.getLogger(MongoDBPersistenceService.class);
|
||||||
|
|
||||||
@ -150,10 +135,206 @@ public class MongoDBPersistenceService implements QueryablePersistenceService {
|
|||||||
return "MongoDB";
|
return "MongoDB";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<PersistenceItemInfo> getItemInfo() {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if we have a database connection.
|
||||||
|
* Also tests if communication with the MongoDB-Server is available.
|
||||||
|
*
|
||||||
|
* @return true if connection has been established, false otherwise
|
||||||
|
*/
|
||||||
|
private synchronized boolean isConnected() {
|
||||||
|
MongoClient localCl = cl;
|
||||||
|
if (localCl == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also check if the connection is valid.
|
||||||
|
// Network problems may cause failure sometimes,
|
||||||
|
// even if the connection object was successfully created before.
|
||||||
|
try {
|
||||||
|
localCl.listDatabaseNames().first();
|
||||||
|
return true;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (Re)connects to the database
|
||||||
|
*
|
||||||
|
* @return True, if the connection was successfully established.
|
||||||
|
*/
|
||||||
|
private synchronized boolean tryConnectToDatabase() {
|
||||||
|
if (isConnected()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
logger.debug("Connect MongoDB");
|
||||||
|
disconnectFromDatabase();
|
||||||
|
|
||||||
|
this.cl = MongoClients.create(this.url);
|
||||||
|
MongoClient localCl = this.cl;
|
||||||
|
|
||||||
|
// The MongoDB driver always succeeds in creating the connection.
|
||||||
|
// We have to actually force it to test the connection to try to connect to the server.
|
||||||
|
if (localCl != null) {
|
||||||
|
localCl.listDatabaseNames().first();
|
||||||
|
logger.debug("Connect MongoDB ... done");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Failed to connect to database {}: {}", this.url, e.getMessage(), e);
|
||||||
|
disconnectFromDatabase();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the currently valid database.
|
||||||
|
*
|
||||||
|
* @return The database object
|
||||||
|
*/
|
||||||
|
private synchronized @Nullable MongoClient getDatabase() {
|
||||||
|
return cl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connects to the Collection
|
||||||
|
*
|
||||||
|
* @return The collection object when collection creation was successful. Null otherwise.
|
||||||
|
*/
|
||||||
|
private @Nullable MongoCollection<Document> connectToCollection(String collectionName) {
|
||||||
|
try {
|
||||||
|
@Nullable
|
||||||
|
MongoClient db = getDatabase();
|
||||||
|
|
||||||
|
if (db == null) {
|
||||||
|
logger.error("Failed to connect to collection {}: Connection not ready", collectionName);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
MongoCollection<Document> mongoCollection = db.getDatabase(this.db).getCollection(collectionName);
|
||||||
|
|
||||||
|
Document idx = new Document();
|
||||||
|
idx.append(MongoDBFields.FIELD_ITEM, 1).append(MongoDBFields.FIELD_TIMESTAMP, 1);
|
||||||
|
mongoCollection.createIndex(idx);
|
||||||
|
|
||||||
|
return mongoCollection;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Failed to connect to collection {}: {}", collectionName, e.getMessage(), e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnects from the database
|
||||||
|
*/
|
||||||
|
private synchronized void disconnectFromDatabase() {
|
||||||
|
MongoClient localCl = cl;
|
||||||
|
if (localCl != null) {
|
||||||
|
localCl.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
cl = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterable<HistoricItem> query(FilterCriteria filter) {
|
||||||
|
MongoCollection<Document> collection = prepareCollection(filter);
|
||||||
|
// If collection creation failed, return nothing.
|
||||||
|
if (collection == null) {
|
||||||
|
// Logging is done in connectToCollection()
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Document query = createQuery(filter);
|
||||||
|
if (query == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
String realItemName = filter.getItemName();
|
||||||
|
if (realItemName == null) {
|
||||||
|
logger.warn("Item name is missing in filter {}", filter);
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Item item = getItem(realItemName);
|
||||||
|
if (item == null) {
|
||||||
|
logger.warn("Item {} not found", realItemName);
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
List<HistoricItem> items = new ArrayList<>();
|
||||||
|
|
||||||
|
logger.debug("Query: {}", query);
|
||||||
|
|
||||||
|
Integer sortDir = (filter.getOrdering() == Ordering.ASCENDING) ? 1 : -1;
|
||||||
|
MongoCursor<Document> cursor = null;
|
||||||
|
try {
|
||||||
|
cursor = collection.find(query).sort(new Document(MongoDBFields.FIELD_TIMESTAMP, sortDir))
|
||||||
|
.skip(filter.getPageNumber() * filter.getPageSize()).limit(filter.getPageSize()).iterator();
|
||||||
|
|
||||||
|
while (cursor.hasNext()) {
|
||||||
|
Document obj = cursor.next();
|
||||||
|
|
||||||
|
final State state = MongoDBTypeConversions.getStateFromDocument(item, obj);
|
||||||
|
|
||||||
|
items.add(new MongoDBItem(realItemName, state, ZonedDateTime
|
||||||
|
.ofInstant(obj.getDate(MongoDBFields.FIELD_TIMESTAMP).toInstant(), ZoneId.systemDefault())));
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (cursor != null) {
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
private @Nullable Item getItem(String itemName) {
|
||||||
|
try {
|
||||||
|
return itemRegistry.getItem(itemName);
|
||||||
|
} catch (ItemNotFoundException e1) {
|
||||||
|
logger.error("Unable to get item type for {}", itemName);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<PersistenceStrategy> getDefaultStrategies() {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void store(Item item, @Nullable String alias) {
|
public void store(Item item, @Nullable String alias) {
|
||||||
|
store(item, new Date(), item.getState(), alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void store(Item item) {
|
||||||
|
store(item, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void store(Item item, ZonedDateTime date, State state) {
|
||||||
|
store(item, date, state, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void store(Item item, ZonedDateTime date, State state, @Nullable String alias) {
|
||||||
|
Date dateConverted = Date.from(date.toInstant());
|
||||||
|
store(item, dateConverted, state, alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void store(Item item, Date date, State state, @Nullable String alias) {
|
||||||
// Don't log undefined/uninitialized data
|
// Don't log undefined/uninitialized data
|
||||||
if (item.getState() instanceof UnDefType) {
|
if (state instanceof UnDefType) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,7 +357,7 @@ public class MongoDBPersistenceService implements QueryablePersistenceService {
|
|||||||
String collectionName = collectionPerItem ? realItemName : this.collection;
|
String collectionName = collectionPerItem ? realItemName : this.collection;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
DBCollection collection = connectToCollection(collectionName);
|
MongoCollection<Document> collection = connectToCollection(collectionName);
|
||||||
|
|
||||||
if (collection == null) {
|
if (collection == null) {
|
||||||
// Logging is done in connectToCollection()
|
// Logging is done in connectToCollection()
|
||||||
@ -184,270 +365,123 @@ public class MongoDBPersistenceService implements QueryablePersistenceService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String name = (alias != null) ? alias : realItemName;
|
String name = (alias != null) ? alias : realItemName;
|
||||||
Object value = this.convertValue(item.getState());
|
Object value = MongoDBTypeConversions.convertValue(state);
|
||||||
|
|
||||||
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);
|
|
||||||
collection.save(obj);
|
|
||||||
|
|
||||||
|
Document obj = new Document();
|
||||||
|
obj.put(MongoDBFields.FIELD_ID, new ObjectId());
|
||||||
|
obj.put(MongoDBFields.FIELD_ITEM, name);
|
||||||
|
obj.put(MongoDBFields.FIELD_REALNAME, realItemName);
|
||||||
|
obj.put(MongoDBFields.FIELD_TIMESTAMP, date);
|
||||||
|
obj.put(MongoDBFields.FIELD_VALUE, value);
|
||||||
|
if (item instanceof NumberItem && state instanceof QuantityType<?>) {
|
||||||
|
obj.put(MongoDBFields.FIELD_UNIT, ((QuantityType<?>) state).getUnit().toString());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
collection.insertOne(obj);
|
||||||
|
} catch (org.bson.BsonMaximumSizeExceededException e) {
|
||||||
|
logger.error("Document size exceeds maximum size of 16MB. Item {} not persisted.", name);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
logger.debug("MongoDB save {}={}", name, value);
|
logger.debug("MongoDB save {}={}", name, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Object convertValue(State state) {
|
@Nullable
|
||||||
Object value;
|
public MongoCollection<Document> prepareCollection(FilterCriteria filter) {
|
||||||
if (state instanceof PercentType type) {
|
if (!initialized || !tryConnectToDatabase()) {
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void store(Item item) {
|
|
||||||
store(item, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<PersistenceItemInfo> getItemInfo() {
|
|
||||||
return Collections.emptySet();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if we have a database connection.
|
|
||||||
* Also tests if communication with the MongoDB-Server is available.
|
|
||||||
*
|
|
||||||
* @return true if connection has been established, false otherwise
|
|
||||||
*/
|
|
||||||
private synchronized boolean isConnected() {
|
|
||||||
if (cl == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Also check if the connection is valid.
|
|
||||||
// Network problems may cause failure sometimes,
|
|
||||||
// even if the connection object was successfully created before.
|
|
||||||
try {
|
|
||||||
cl.getAddress();
|
|
||||||
return true;
|
|
||||||
} catch (Exception ex) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* (Re)connects to the database
|
|
||||||
*
|
|
||||||
* @return True, if the connection was successfully established.
|
|
||||||
*/
|
|
||||||
private synchronized boolean tryConnectToDatabase() {
|
|
||||||
if (isConnected()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
logger.debug("Connect MongoDB");
|
|
||||||
disconnectFromDatabase();
|
|
||||||
|
|
||||||
this.cl = new MongoClient(new MongoClientURI(this.url));
|
|
||||||
|
|
||||||
// The mongo always succeeds in creating the connection.
|
|
||||||
// We have to actually force it to test the connection to try to connect to the server.
|
|
||||||
cl.getAddress();
|
|
||||||
|
|
||||||
logger.debug("Connect MongoDB ... done");
|
|
||||||
return true;
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("Failed to connect to database {}: {}", this.url, e.getMessage(), e);
|
|
||||||
disconnectFromDatabase();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches the currently valid database.
|
|
||||||
*
|
|
||||||
* @return The database object
|
|
||||||
*/
|
|
||||||
private synchronized @Nullable MongoClient getDatabase() {
|
|
||||||
return cl;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connects to the Collection
|
|
||||||
*
|
|
||||||
* @return The collection object when collection creation was successful. Null otherwise.
|
|
||||||
*/
|
|
||||||
private @Nullable DBCollection connectToCollection(String collectionName) {
|
|
||||||
try {
|
|
||||||
@Nullable
|
|
||||||
MongoClient db = getDatabase();
|
|
||||||
|
|
||||||
if (db == null) {
|
|
||||||
logger.error("Failed to connect to collection {}: Connection not ready", collectionName);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
DBCollection mongoCollection = db.getDB(this.db).getCollection(collectionName);
|
|
||||||
|
|
||||||
BasicDBObject idx = new BasicDBObject();
|
|
||||||
idx.append(FIELD_ITEM, 1).append(FIELD_TIMESTAMP, 1);
|
|
||||||
mongoCollection.createIndex(idx);
|
|
||||||
|
|
||||||
return mongoCollection;
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("Failed to connect to collection {}: {}", collectionName, e.getMessage(), e);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disconnects from the database
|
|
||||||
*/
|
|
||||||
private synchronized void disconnectFromDatabase() {
|
|
||||||
if (this.cl != null) {
|
|
||||||
this.cl.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
cl = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Iterable<HistoricItem> query(FilterCriteria filter) {
|
|
||||||
if (!initialized) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!tryConnectToDatabase()) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
String realItemName = filter.getItemName();
|
String realItemName = filter.getItemName();
|
||||||
if (realItemName == null) {
|
if (realItemName == null) {
|
||||||
logger.warn("Item name is missing in filter {}", filter);
|
logger.warn("Item name is missing in filter {}", filter);
|
||||||
return List.of();
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
MongoCollection<Document> collection = getCollection(realItemName);
|
||||||
|
return collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private MongoCollection<Document> getCollection(String realItemName) {
|
||||||
String collectionName = collectionPerItem ? realItemName : this.collection;
|
String collectionName = collectionPerItem ? realItemName : this.collection;
|
||||||
@Nullable
|
@Nullable
|
||||||
DBCollection collection = connectToCollection(collectionName);
|
MongoCollection<Document> collection = connectToCollection(collectionName);
|
||||||
|
|
||||||
// If collection creation failed, return nothing.
|
|
||||||
if (collection == null) {
|
if (collection == null) {
|
||||||
// Logging is done in connectToCollection()
|
// Logging is done in connectToCollection()
|
||||||
return Collections.emptyList();
|
logger.warn("Failed to connect to collection {}", collectionName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
return collection;
|
||||||
Item item = getItem(realItemName);
|
}
|
||||||
|
|
||||||
if (item == null) {
|
@Nullable
|
||||||
logger.warn("Item {} not found", realItemName);
|
private Document createQuery(FilterCriteria filter) {
|
||||||
return Collections.emptyList();
|
String realItemName = filter.getItemName();
|
||||||
|
Document query = new Document();
|
||||||
|
query.put(MongoDBFields.FIELD_ITEM, realItemName);
|
||||||
|
|
||||||
|
if (!addStateToQuery(filter, query) || !addDateToQuery(filter, query)) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<HistoricItem> items = new ArrayList<>();
|
return query;
|
||||||
BasicDBObject query = new BasicDBObject();
|
}
|
||||||
if (filter.getItemName() != null) {
|
|
||||||
query.put(FIELD_ITEM, filter.getItemName());
|
private boolean addStateToQuery(FilterCriteria filter, Document query) {
|
||||||
}
|
|
||||||
State filterState = filter.getState();
|
State filterState = filter.getState();
|
||||||
if (filterState != null && filter.getOperator() != null) {
|
if (filterState != null) {
|
||||||
@Nullable
|
String op = MongoDBTypeConversions.convertOperator(filter.getOperator());
|
||||||
String op = convertOperator(filter.getOperator());
|
|
||||||
|
|
||||||
if (op == null) {
|
if (op == null) {
|
||||||
logger.error("Failed to convert operator {} to MongoDB operator", filter.getOperator());
|
logger.error("Failed to convert operator {} to MongoDB operator", filter.getOperator());
|
||||||
return Collections.emptyList();
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Object value = convertValue(filterState);
|
Object value = MongoDBTypeConversions.convertValue(filterState);
|
||||||
query.put(FIELD_VALUE, new BasicDBObject(op, value));
|
query.put(MongoDBFields.FIELD_VALUE, new Document(op, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
BasicDBObject dateQueries = new BasicDBObject();
|
return true;
|
||||||
if (filter.getBeginDate() != null) {
|
}
|
||||||
dateQueries.put("$gte", Date.from(filter.getBeginDate().toInstant()));
|
|
||||||
|
private boolean addDateToQuery(FilterCriteria filter, Document query) {
|
||||||
|
Document dateQueries = new Document();
|
||||||
|
ZonedDateTime beginDate = filter.getBeginDate();
|
||||||
|
if (beginDate != null) {
|
||||||
|
dateQueries.put("$gte", Date.from(beginDate.toInstant()));
|
||||||
}
|
}
|
||||||
if (filter.getEndDate() != null) {
|
ZonedDateTime endDate = filter.getEndDate();
|
||||||
dateQueries.put("$lte", Date.from(filter.getEndDate().toInstant()));
|
if (endDate != null) {
|
||||||
|
dateQueries.put("$lte", Date.from(endDate.toInstant()));
|
||||||
}
|
}
|
||||||
if (!dateQueries.isEmpty()) {
|
if (!dateQueries.isEmpty()) {
|
||||||
query.put(FIELD_TIMESTAMP, dateQueries);
|
query.put(MongoDBFields.FIELD_TIMESTAMP, dateQueries);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean remove(FilterCriteria filter) {
|
||||||
|
MongoCollection<Document> collection = prepareCollection(filter);
|
||||||
|
// If collection creation failed, return nothing.
|
||||||
|
if (collection == null) {
|
||||||
|
// Logging is done in connectToCollection()
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Document query = createQuery(filter);
|
||||||
|
if (query == null) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug("Query: {}", query);
|
logger.debug("Query: {}", query);
|
||||||
|
|
||||||
Integer sortDir = (filter.getOrdering() == Ordering.ASCENDING) ? 1 : -1;
|
DeleteResult result = collection.deleteMany(query);
|
||||||
DBCursor cursor = collection.find(query).sort(new BasicDBObject(FIELD_TIMESTAMP, sortDir))
|
|
||||||
.skip(filter.getPageNumber() * filter.getPageSize()).limit(filter.getPageSize());
|
|
||||||
|
|
||||||
while (cursor.hasNext()) {
|
logger.debug("Deleted {} documents", result.getDeletedCount());
|
||||||
BasicDBObject obj = (BasicDBObject) cursor.next();
|
return true;
|
||||||
|
|
||||||
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,
|
|
||||||
ZonedDateTime.ofInstant(obj.getDate(FIELD_TIMESTAMP).toInstant(), ZoneId.systemDefault())));
|
|
||||||
}
|
|
||||||
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
private @Nullable String convertOperator(Operator operator) {
|
|
||||||
switch (operator) {
|
|
||||||
case EQ:
|
|
||||||
return "$eq";
|
|
||||||
case GT:
|
|
||||||
return "$gt";
|
|
||||||
case GTE:
|
|
||||||
return "$gte";
|
|
||||||
case LT:
|
|
||||||
return "$lt";
|
|
||||||
case LTE:
|
|
||||||
return "$lte";
|
|
||||||
case NEQ:
|
|
||||||
return "$neq";
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private @Nullable Item getItem(String itemName) {
|
|
||||||
try {
|
|
||||||
return itemRegistry.getItem(itemName);
|
|
||||||
} catch (ItemNotFoundException e1) {
|
|
||||||
logger.error("Unable to get item type for {}", itemName);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<PersistenceStrategy> getDefaultStrategies() {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,255 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2024 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.time.ZoneId;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import javax.measure.Unit;
|
||||||
|
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.bson.types.Binary;
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.core.items.GenericItem;
|
||||||
|
import org.openhab.core.items.Item;
|
||||||
|
import org.openhab.core.library.items.CallItem;
|
||||||
|
import org.openhab.core.library.items.ColorItem;
|
||||||
|
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.LocationItem;
|
||||||
|
import org.openhab.core.library.items.NumberItem;
|
||||||
|
import org.openhab.core.library.items.PlayerItem;
|
||||||
|
import org.openhab.core.library.items.RollershutterItem;
|
||||||
|
import org.openhab.core.library.items.StringItem;
|
||||||
|
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.PlayPauseType;
|
||||||
|
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.StringListType;
|
||||||
|
import org.openhab.core.library.types.StringType;
|
||||||
|
import org.openhab.core.persistence.FilterCriteria.Operator;
|
||||||
|
import org.openhab.core.types.State;
|
||||||
|
import org.openhab.core.types.UnDefType;
|
||||||
|
import org.openhab.core.types.util.UnitUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class handles the conversion of types between openHAB and MongoDB.
|
||||||
|
* It provides methods to convert openHAB states to MongoDB compatible types and vice versa.
|
||||||
|
* It also provides a method to convert openHAB filter operators to MongoDB query operators.
|
||||||
|
*
|
||||||
|
* @author René Ulbricht - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class MongoDBTypeConversions {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a MongoDB document to an openHAB state.
|
||||||
|
*
|
||||||
|
* @param item The openHAB item that the state belongs to.
|
||||||
|
* @param doc The MongoDB document to convert.
|
||||||
|
* @return The openHAB state.
|
||||||
|
* @throws IllegalArgumentException If the item type is not supported.
|
||||||
|
*/
|
||||||
|
public static State getStateFromDocument(Item item, Document doc) {
|
||||||
|
BiFunction<Item, Document, State> converter = ITEM_STATE_CONVERTERS.get(item.getClass());
|
||||||
|
if (converter != null) {
|
||||||
|
return converter.apply(item, doc);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Unsupported item type: " + item.getClass().getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts an openHAB filter operator to a MongoDB query operator.
|
||||||
|
*
|
||||||
|
* @param operator The openHAB filter operator to convert.
|
||||||
|
* @return The MongoDB query operator, or null if the operator is not supported.
|
||||||
|
*/
|
||||||
|
public static @Nullable String convertOperator(Operator operator) {
|
||||||
|
return switch (operator) {
|
||||||
|
case EQ -> "$eq";
|
||||||
|
case GT -> "$gt";
|
||||||
|
case GTE -> "$gte";
|
||||||
|
case LT -> "$lt";
|
||||||
|
case LTE -> "$lte";
|
||||||
|
case NEQ -> "$neq";
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts an openHAB state to a MongoDB compatible type.
|
||||||
|
*
|
||||||
|
* @param state The openHAB state to convert.
|
||||||
|
* @return The MongoDB compatible type.
|
||||||
|
*/
|
||||||
|
public static Object convertValue(State state) {
|
||||||
|
return STATE_CONVERTERS.getOrDefault(state.getClass(), State::toString).apply(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(MongoDBTypeConversions.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map of converters that convert openHAB states to MongoDB compatible types.
|
||||||
|
* Each converter is a function that takes an openHAB state and returns an object that can be stored in MongoDB.
|
||||||
|
*/
|
||||||
|
private static final Map<Class<? extends State>, Function<State, Object>> STATE_CONVERTERS = Map.of( //
|
||||||
|
HSBType.class, State::toString, //
|
||||||
|
QuantityType.class, state -> ((QuantityType<?>) state).toBigDecimal().doubleValue(), //
|
||||||
|
PercentType.class, state -> ((PercentType) state).intValue(), //
|
||||||
|
DateTimeType.class, state -> ((DateTimeType) state).getZonedDateTime().toString(), //
|
||||||
|
StringListType.class, State::toString, //
|
||||||
|
DecimalType.class, state -> ((DecimalType) state).toBigDecimal().doubleValue(), //
|
||||||
|
RawType.class, MongoDBTypeConversions::handleRawType//
|
||||||
|
);
|
||||||
|
|
||||||
|
private static Object handleRawType(State state) {
|
||||||
|
RawType rawType = (RawType) state;
|
||||||
|
Document doc = new Document();
|
||||||
|
doc.put(MongoDBFields.FIELD_VALUE_TYPE, rawType.getMimeType());
|
||||||
|
doc.put(MongoDBFields.FIELD_VALUE_DATA, rawType.getBytes());
|
||||||
|
return doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map of converters that convert MongoDB documents to openHAB states.
|
||||||
|
* Each converter is a function that takes an openHAB item and a MongoDB document and returns an openHAB state.
|
||||||
|
*/
|
||||||
|
|
||||||
|
private static final Map<Class<? extends Item>, BiFunction<Item, Document, State>> ITEM_STATE_CONVERTERS = //
|
||||||
|
Map.ofEntries( //
|
||||||
|
Map.entry(NumberItem.class, MongoDBTypeConversions::handleNumberItem),
|
||||||
|
Map.entry(ColorItem.class, MongoDBTypeConversions::handleColorItem),
|
||||||
|
Map.entry(DimmerItem.class, MongoDBTypeConversions::handleDimmerItem),
|
||||||
|
Map.entry(SwitchItem.class,
|
||||||
|
(Item item, Document doc) -> OnOffType.valueOf(doc.getString(MongoDBFields.FIELD_VALUE))),
|
||||||
|
Map.entry(ContactItem.class,
|
||||||
|
(Item item, Document doc) -> OpenClosedType
|
||||||
|
.valueOf(doc.getString(MongoDBFields.FIELD_VALUE))),
|
||||||
|
Map.entry(RollershutterItem.class, MongoDBTypeConversions::handleRollershutterItem),
|
||||||
|
Map.entry(DateTimeItem.class, MongoDBTypeConversions::handleDateTimeItem),
|
||||||
|
Map.entry(LocationItem.class,
|
||||||
|
(Item item, Document doc) -> new PointType(doc.getString(MongoDBFields.FIELD_VALUE))),
|
||||||
|
Map.entry(PlayerItem.class,
|
||||||
|
(Item item, Document doc) -> PlayPauseType
|
||||||
|
.valueOf(doc.getString(MongoDBFields.FIELD_VALUE))),
|
||||||
|
Map.entry(CallItem.class,
|
||||||
|
(Item item, Document doc) -> new StringListType(doc.getString(MongoDBFields.FIELD_VALUE))),
|
||||||
|
Map.entry(ImageItem.class, MongoDBTypeConversions::handleImageItem), //
|
||||||
|
Map.entry(StringItem.class,
|
||||||
|
(Item item, Document doc) -> new StringType(doc.getString(MongoDBFields.FIELD_VALUE))),
|
||||||
|
Map.entry(GenericItem.class,
|
||||||
|
(Item item, Document doc) -> new StringType(doc.getString(MongoDBFields.FIELD_VALUE)))//
|
||||||
|
);
|
||||||
|
|
||||||
|
private static State handleNumberItem(Item item, Document doc) {
|
||||||
|
NumberItem numberItem = (NumberItem) item;
|
||||||
|
Unit<?> unit = numberItem.getUnit();
|
||||||
|
Object value = doc.get(MongoDBFields.FIELD_VALUE);
|
||||||
|
if (value == null) {
|
||||||
|
return UnDefType.UNDEF;
|
||||||
|
}
|
||||||
|
if (doc.containsKey(MongoDBFields.FIELD_UNIT)) {
|
||||||
|
String unitString = doc.getString(MongoDBFields.FIELD_UNIT);
|
||||||
|
Unit<?> docUnit = UnitUtils.parseUnit(unitString);
|
||||||
|
if (docUnit != null) {
|
||||||
|
unit = docUnit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (value instanceof String) {
|
||||||
|
return new QuantityType<>(value.toString());
|
||||||
|
}
|
||||||
|
if (unit != null) {
|
||||||
|
return new QuantityType<>(((Number) value).doubleValue(), unit);
|
||||||
|
} else {
|
||||||
|
return new DecimalType(((Number) value).doubleValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static State handleColorItem(Item item, Document doc) {
|
||||||
|
Object value = doc.get(MongoDBFields.FIELD_VALUE);
|
||||||
|
if (value instanceof String) {
|
||||||
|
return new HSBType(value.toString());
|
||||||
|
} else {
|
||||||
|
logger.warn("HSBType ({}) value is not a valid string: {}", doc.getString(MongoDBFields.FIELD_REALNAME),
|
||||||
|
value);
|
||||||
|
return new HSBType("0,0,0");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static State handleDimmerItem(Item item, Document doc) {
|
||||||
|
Object value = doc.get(MongoDBFields.FIELD_VALUE);
|
||||||
|
if (value == null) {
|
||||||
|
return UnDefType.UNDEF;
|
||||||
|
}
|
||||||
|
if (value instanceof Integer) {
|
||||||
|
return new PercentType((Integer) value);
|
||||||
|
} else {
|
||||||
|
return new PercentType(((Number) value).intValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static State handleRollershutterItem(Item item, Document doc) {
|
||||||
|
Object value = doc.get(MongoDBFields.FIELD_VALUE);
|
||||||
|
if (value == null) {
|
||||||
|
return UnDefType.UNDEF;
|
||||||
|
}
|
||||||
|
if (value instanceof Integer) {
|
||||||
|
return new PercentType((Integer) value);
|
||||||
|
} else {
|
||||||
|
return new PercentType(((Number) value).intValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static State handleDateTimeItem(Item item, Document doc) {
|
||||||
|
Object value = doc.get(MongoDBFields.FIELD_VALUE);
|
||||||
|
if (value == null) {
|
||||||
|
return UnDefType.UNDEF;
|
||||||
|
}
|
||||||
|
if (value instanceof String) {
|
||||||
|
return new DateTimeType(ZonedDateTime.parse(doc.getString(MongoDBFields.FIELD_VALUE)));
|
||||||
|
} else {
|
||||||
|
return new DateTimeType(ZonedDateTime.ofInstant(((Date) value).toInstant(), ZoneId.systemDefault()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static State handleImageItem(Item item, Document doc) {
|
||||||
|
Object value = doc.get(MongoDBFields.FIELD_VALUE);
|
||||||
|
if (value instanceof Document) {
|
||||||
|
Document fieldValue = (Document) value;
|
||||||
|
String type = fieldValue.getString(MongoDBFields.FIELD_VALUE_TYPE);
|
||||||
|
Binary data = fieldValue.get(MongoDBFields.FIELD_VALUE_DATA, Binary.class);
|
||||||
|
return new RawType(data.getData(), type);
|
||||||
|
} else {
|
||||||
|
logger.warn("ImageItem ({}) value is not a Document: {}", doc.getString(MongoDBFields.FIELD_REALNAME),
|
||||||
|
value);
|
||||||
|
return new RawType(new byte[0], "application/octet-stream");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,444 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2024 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 static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Hashtable;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.bson.types.ObjectId;
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
import org.openhab.core.i18n.UnitProvider;
|
||||||
|
import org.openhab.core.internal.i18n.I18nProviderImpl;
|
||||||
|
import org.openhab.core.items.GenericItem;
|
||||||
|
import org.openhab.core.items.Item;
|
||||||
|
import org.openhab.core.items.ItemRegistry;
|
||||||
|
import org.openhab.core.library.items.CallItem;
|
||||||
|
import org.openhab.core.library.items.ColorItem;
|
||||||
|
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.LocationItem;
|
||||||
|
import org.openhab.core.library.items.NumberItem;
|
||||||
|
import org.openhab.core.library.items.PlayerItem;
|
||||||
|
import org.openhab.core.library.items.RollershutterItem;
|
||||||
|
import org.openhab.core.library.items.StringItem;
|
||||||
|
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.PlayPauseType;
|
||||||
|
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.StringListType;
|
||||||
|
import org.openhab.core.library.types.StringType;
|
||||||
|
import org.openhab.core.library.unit.SIUnits;
|
||||||
|
import org.openhab.core.persistence.FilterCriteria;
|
||||||
|
import org.openhab.core.types.State;
|
||||||
|
import org.osgi.framework.BundleContext;
|
||||||
|
import org.osgi.service.component.ComponentContext;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.testcontainers.DockerClientFactory;
|
||||||
|
|
||||||
|
import com.mongodb.client.MongoClient;
|
||||||
|
import com.mongodb.client.MongoClients;
|
||||||
|
import com.mongodb.client.MongoCollection;
|
||||||
|
import com.mongodb.client.MongoDatabase;
|
||||||
|
|
||||||
|
import ch.qos.logback.classic.Level;
|
||||||
|
import ch.qos.logback.classic.Logger;
|
||||||
|
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||||
|
import ch.qos.logback.core.read.ListAppender;
|
||||||
|
import de.bwaldvogel.mongo.backend.memory.MemoryBackend;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class provides helper methods to create test items.
|
||||||
|
*
|
||||||
|
* @author René Ulbricht - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class DataCreationHelper {
|
||||||
|
|
||||||
|
protected static final UnitProvider UNIT_PROVIDER;
|
||||||
|
static {
|
||||||
|
ComponentContext context = Mockito.mock(ComponentContext.class);
|
||||||
|
BundleContext bundleContext = Mockito.mock(BundleContext.class);
|
||||||
|
Hashtable<String, Object> properties = new Hashtable<>();
|
||||||
|
properties.put("measurementSystem", SIUnits.MEASUREMENT_SYSTEM_NAME);
|
||||||
|
when(context.getProperties()).thenReturn(properties);
|
||||||
|
when(context.getBundleContext()).thenReturn(bundleContext);
|
||||||
|
UNIT_PROVIDER = new I18nProviderImpl(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a NumberItem with a given name and value.
|
||||||
|
*
|
||||||
|
* @param name The name of the NumberItem.
|
||||||
|
* @param value The value of the NumberItem.
|
||||||
|
* @return The created NumberItem.
|
||||||
|
*/
|
||||||
|
public static NumberItem createNumberItem(String name, Number value) {
|
||||||
|
return createItem(NumberItem.class, name, new DecimalType(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a StringItem with a given name and value.
|
||||||
|
*
|
||||||
|
* @param name The name of the StringItem.
|
||||||
|
* @param value The value of the StringItem.
|
||||||
|
* @return The created StringItem.
|
||||||
|
*/
|
||||||
|
public static StringItem createStringItem(String name, String value) {
|
||||||
|
return createItem(StringItem.class, name, new StringType(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance of a NumberItem with a unit type and sets its state.
|
||||||
|
*
|
||||||
|
* @param itemType The Class object representing the type of the item to create.
|
||||||
|
* @param unitType The string representation of the unit type to set on the new item.
|
||||||
|
* @param name The name to give to the new item.
|
||||||
|
* @param state The state to set on the new item.
|
||||||
|
* @return The newly created item.
|
||||||
|
* @throws RuntimeException if an error occurs while creating the item or setting its state.
|
||||||
|
*/
|
||||||
|
public static NumberItem createNumberItem(String unitType, String name, State state) {
|
||||||
|
NumberItem item = new NumberItem(unitType, name, UNIT_PROVIDER);
|
||||||
|
item.setState(state);
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance of a specific GenericItem subclass and sets its state.
|
||||||
|
*
|
||||||
|
* @param <T> The type of the item to create. This must be a subclass of GenericItem.
|
||||||
|
* @param <S> The type of the state to set. This must be a subclass of State.
|
||||||
|
* @param itemType The Class object representing the type of the item to create.
|
||||||
|
* @param name The name to give to the new item.
|
||||||
|
* @param state The state to set on the new item.
|
||||||
|
* @return The newly created item.
|
||||||
|
* @throws RuntimeException if an error occurs while creating the item or setting its state.
|
||||||
|
*/
|
||||||
|
public static <T extends GenericItem, S extends State> T createItem(Class<T> itemType, String name, S state) {
|
||||||
|
try {
|
||||||
|
if (state == null) {
|
||||||
|
throw new IllegalArgumentException("State must not be null");
|
||||||
|
}
|
||||||
|
T item = itemType.getDeclaredConstructor(String.class).newInstance(name);
|
||||||
|
if (item == null) {
|
||||||
|
throw new RuntimeException("Could not create item");
|
||||||
|
}
|
||||||
|
item.setState(state);
|
||||||
|
return item;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RawType createFakeImage(int size) {
|
||||||
|
byte[] data = new byte[size];
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
data[i] = (byte) (i % 256);
|
||||||
|
}
|
||||||
|
return new RawType(data, "image/png");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a stream of arguments for parameterized tests. To test various image sizes
|
||||||
|
*
|
||||||
|
* @return A stream of arguments for parameterized tests.
|
||||||
|
*/
|
||||||
|
public static Stream<Arguments> provideOpenhabImageItemsInDifferentSizes() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of(DataCreationHelper.createItem(ImageItem.class, "ImageItem1kB", createFakeImage(1024))),
|
||||||
|
Arguments.of(
|
||||||
|
DataCreationHelper.createItem(ImageItem.class, "ImageItem1MB", createFakeImage(1024 * 1024))),
|
||||||
|
Arguments.of(DataCreationHelper.createItem(ImageItem.class, "ImageItem10MB",
|
||||||
|
createFakeImage(10 * 1024 * 1024))),
|
||||||
|
Arguments.of(DataCreationHelper.createItem(ImageItem.class, "ImageItem20MB",
|
||||||
|
createFakeImage(20 * 1024 * 1024))));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a stream of arguments for parameterized tests. Each argument is an instance of a specific
|
||||||
|
* GenericItem subclass with a set state.
|
||||||
|
*
|
||||||
|
* @return A stream of arguments for parameterized tests.
|
||||||
|
*/
|
||||||
|
public static Stream<Arguments> provideOpenhabItemTypes() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of(
|
||||||
|
DataCreationHelper.createItem(StringItem.class, "StringItem", new StringType("StringValue"))),
|
||||||
|
Arguments.of(DataCreationHelper.createItem(NumberItem.class, "NumberItem", new DecimalType(123.45))),
|
||||||
|
Arguments.of(DataCreationHelper.createItem(DimmerItem.class, "DimmerItem", new PercentType(50))),
|
||||||
|
Arguments.of(DataCreationHelper.createItem(SwitchItem.class, "SwitchItemON", OnOffType.ON)),
|
||||||
|
Arguments.of(DataCreationHelper.createItem(SwitchItem.class, "SwitchItemOFF", OnOffType.OFF)),
|
||||||
|
Arguments.of(DataCreationHelper.createItem(ContactItem.class, "ContactItemOPEN", OpenClosedType.OPEN)),
|
||||||
|
Arguments.of(
|
||||||
|
DataCreationHelper.createItem(ContactItem.class, "ContactItemCLOSED", OpenClosedType.CLOSED)),
|
||||||
|
Arguments.of(DataCreationHelper.createItem(RollershutterItem.class, "RollershutterItem",
|
||||||
|
new PercentType(30))),
|
||||||
|
Arguments.of(DataCreationHelper.createItem(DateTimeItem.class, "DateTimeItem",
|
||||||
|
new DateTimeType(ZonedDateTime.now()))),
|
||||||
|
Arguments.of(DataCreationHelper.createItem(ColorItem.class, "ColorItem", new HSBType("180,100,100"))),
|
||||||
|
Arguments.of(
|
||||||
|
DataCreationHelper.createItem(LocationItem.class, "LocationItem", new PointType("51.0,0.0"))),
|
||||||
|
Arguments.of(DataCreationHelper.createItem(PlayerItem.class, "PlayerItem", PlayPauseType.PLAY)),
|
||||||
|
Arguments.of(DataCreationHelper.createItem(CallItem.class, "CallItem",
|
||||||
|
new StringListType("+49 123 456 789"))),
|
||||||
|
Arguments.of(DataCreationHelper.createItem(ImageItem.class, "ImageItem",
|
||||||
|
new RawType(new byte[] { 0x00, 0x01, 0x02 }, "image/png"))),
|
||||||
|
Arguments.of(DataCreationHelper.createNumberItem("Number:Energy", "NumberItemCelcius",
|
||||||
|
new QuantityType<>("25.00 MWh"))),
|
||||||
|
Arguments.of(DataCreationHelper.createNumberItem("Number:Temperature", "NumberItemCelcius",
|
||||||
|
new QuantityType<>("25.00 °F"))));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a stream of arguments to be used for parameterized tests.
|
||||||
|
*
|
||||||
|
* Each argument is a DatabaseTestContainer instance. Some instances use a MemoryBackend,
|
||||||
|
* while others use a MongoDBContainer with a specific MongoDB version.
|
||||||
|
* In case there is no Docker available, only the MemoryBackend is used.
|
||||||
|
*
|
||||||
|
* @return A stream of Arguments, each containing a DatabaseTestContainer instance.
|
||||||
|
*/
|
||||||
|
public static Stream<Arguments> provideDatabaseBackends() {
|
||||||
|
if (DockerClientFactory.instance().isDockerAvailable()) {
|
||||||
|
// If Docker is available, create a stream of Arguments with all backends
|
||||||
|
return Stream.of(
|
||||||
|
// Create a DatabaseTestContainer with a MemoryBackend
|
||||||
|
Arguments.of(new DatabaseTestContainer(new MemoryBackend())),
|
||||||
|
// Create DatabaseTestContainers with MongoDBContainers of specific versions
|
||||||
|
Arguments.of(new DatabaseTestContainer("mongo:3.6")),
|
||||||
|
Arguments.of(new DatabaseTestContainer("mongo:4.4")),
|
||||||
|
Arguments.of(new DatabaseTestContainer("mongo:5.0")),
|
||||||
|
Arguments.of(new DatabaseTestContainer("mongo:6.0")));
|
||||||
|
} else {
|
||||||
|
// If Docker is not available, create a stream of Arguments with only the MemoryBackend
|
||||||
|
return Stream.of(Arguments.of(new DatabaseTestContainer(new MemoryBackend())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Document for a given item name, value, and timestamp.
|
||||||
|
*
|
||||||
|
* @param itemName The name of the item.
|
||||||
|
* @param value The value of the item.
|
||||||
|
* @param timestamp The timestamp of the item.
|
||||||
|
* @return The created Document.
|
||||||
|
*/
|
||||||
|
public static Document createDocument(String itemName, double value, LocalDate timestamp) {
|
||||||
|
Document obj = new Document();
|
||||||
|
obj.put(MongoDBFields.FIELD_ID, new ObjectId());
|
||||||
|
obj.put(MongoDBFields.FIELD_ITEM, itemName);
|
||||||
|
obj.put(MongoDBFields.FIELD_REALNAME, itemName);
|
||||||
|
obj.put(MongoDBFields.FIELD_TIMESTAMP, timestamp);
|
||||||
|
obj.put(MongoDBFields.FIELD_VALUE, value);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a FilterCriteria for a given item name.
|
||||||
|
*
|
||||||
|
* @param itemName The name of the item.
|
||||||
|
* @return The created FilterCriteria.
|
||||||
|
*/
|
||||||
|
public static FilterCriteria createFilterCriteria(String itemName) {
|
||||||
|
return createFilterCriteria(itemName, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a FilterCriteria for a given item name, begin date, and end date.
|
||||||
|
*
|
||||||
|
* @param itemName The name of the item.
|
||||||
|
* @param beginDate The begin date of the FilterCriteria.
|
||||||
|
* @param endDate The end date of the FilterCriteria.
|
||||||
|
* @return The created FilterCriteria.
|
||||||
|
*/
|
||||||
|
public static FilterCriteria createFilterCriteria(String itemName, @Nullable ZonedDateTime beginDate,
|
||||||
|
@Nullable ZonedDateTime endDate) {
|
||||||
|
FilterCriteria filter = new FilterCriteria();
|
||||||
|
filter.setItemName(itemName);
|
||||||
|
filter.setPageSize(10);
|
||||||
|
filter.setPageNumber(0);
|
||||||
|
filter.setOrdering(FilterCriteria.Ordering.ASCENDING);
|
||||||
|
if (beginDate != null) {
|
||||||
|
filter.setBeginDate(beginDate);
|
||||||
|
}
|
||||||
|
if (endDate != null) {
|
||||||
|
filter.setEndDate(endDate);
|
||||||
|
}
|
||||||
|
return filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up a MongoDB instance for testing.
|
||||||
|
*
|
||||||
|
* @param collectionName The name of the MongoDB collection to be used for testing.
|
||||||
|
* @param dbContainer The container running the MongoDB instance.
|
||||||
|
* @return A SetupResult object containing the MongoDBPersistenceService, the database, the bundle context, the
|
||||||
|
* configuration map, the item registry, and the database name.
|
||||||
|
*/
|
||||||
|
public static SetupResult setupMongoDB(@Nullable String collectionName, DatabaseTestContainer dbContainer) {
|
||||||
|
// Start the database container
|
||||||
|
dbContainer.start();
|
||||||
|
|
||||||
|
// Mock the ItemRegistry and BundleContext
|
||||||
|
ItemRegistry itemRegistry = Mockito.mock(ItemRegistry.class);
|
||||||
|
BundleContext bundleContext = Mockito.mock(BundleContext.class);
|
||||||
|
|
||||||
|
// When getService is called on the bundleContext, return the mocked itemRegistry
|
||||||
|
when(bundleContext.getService(any())).thenReturn(itemRegistry);
|
||||||
|
|
||||||
|
// Create a new MongoDBPersistenceService instance
|
||||||
|
MongoDBPersistenceService service = new MongoDBPersistenceService(itemRegistry);
|
||||||
|
|
||||||
|
// Create a configuration map for the MongoDBPersistenceService
|
||||||
|
Map<String, Object> config = new HashMap<>();
|
||||||
|
config.put("url", dbContainer.getConnectionString());
|
||||||
|
String dbname = UUID.randomUUID().toString();
|
||||||
|
config.put("database", dbname);
|
||||||
|
if (collectionName != null) {
|
||||||
|
config.put("collection", collectionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a MongoClient connected to the mock server
|
||||||
|
MongoClient mongoClient = MongoClients.create(dbContainer.getConnectionString());
|
||||||
|
|
||||||
|
// Create a database and collection
|
||||||
|
MongoDatabase database = mongoClient.getDatabase(dbname);
|
||||||
|
|
||||||
|
// Setup logger to capture log events
|
||||||
|
Logger logger = (Logger) LoggerFactory.getLogger(MongoDBPersistenceService.class);
|
||||||
|
ListAppender<ILoggingEvent> listAppender = new ListAppender<>();
|
||||||
|
listAppender.start();
|
||||||
|
logger.addAppender(listAppender);
|
||||||
|
logger.setLevel(Level.WARN);
|
||||||
|
|
||||||
|
// Return a SetupResult object containing the service, database, bundle context, config, item registry, and
|
||||||
|
// database name
|
||||||
|
return new SetupResult(service, database, bundleContext, config, itemRegistry, dbname);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up a logger to capture log events.
|
||||||
|
*
|
||||||
|
* @param loggerClass The class that the logger is for.
|
||||||
|
* @param level The level of the logger.
|
||||||
|
* @return The list appender attached to the logger.
|
||||||
|
*/
|
||||||
|
public static ListAppender<ILoggingEvent> setupLogger(Class<?> loggerClass, Level level) {
|
||||||
|
Logger logger = (Logger) LoggerFactory.getLogger(loggerClass);
|
||||||
|
ListAppender<ILoggingEvent> listAppender = new ListAppender<>();
|
||||||
|
listAppender.start();
|
||||||
|
logger.addAppender(listAppender);
|
||||||
|
logger.setLevel(level); // Set log level
|
||||||
|
return listAppender;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object convertValue(State state) {
|
||||||
|
Object value;
|
||||||
|
if (state instanceof PercentType) {
|
||||||
|
PercentType type = (PercentType) state;
|
||||||
|
value = type.toBigDecimal().doubleValue();
|
||||||
|
} else if (state instanceof DateTimeType) {
|
||||||
|
DateTimeType type = (DateTimeType) state;
|
||||||
|
value = Date.from(type.getZonedDateTime().toInstant());
|
||||||
|
} else if (state instanceof DecimalType) {
|
||||||
|
DecimalType type = (DecimalType) state;
|
||||||
|
value = type.toBigDecimal().doubleValue();
|
||||||
|
} else {
|
||||||
|
value = state.toString();
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the old data of an item into a MongoDB collection.
|
||||||
|
*
|
||||||
|
* @param collection The MongoDB collection where the data will be stored.
|
||||||
|
* @param realItemName The real name of the item.
|
||||||
|
* @param state The state of the item.
|
||||||
|
*/
|
||||||
|
public static void storeOldData(MongoCollection<Document> collection, String realItemName, State state) {
|
||||||
|
// use the old way to store data
|
||||||
|
Object value = convertValue(state);
|
||||||
|
|
||||||
|
Document obj = new Document();
|
||||||
|
obj.put(MongoDBFields.FIELD_ID, new ObjectId());
|
||||||
|
obj.put(MongoDBFields.FIELD_ITEM, realItemName);
|
||||||
|
obj.put(MongoDBFields.FIELD_REALNAME, realItemName);
|
||||||
|
obj.put(MongoDBFields.FIELD_TIMESTAMP, new Date());
|
||||||
|
obj.put(MongoDBFields.FIELD_VALUE, value);
|
||||||
|
collection.insertOne(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<PersistenceTestItem> createTestData(MongoDBPersistenceService service, String... itemNames) {
|
||||||
|
// Prepare a list to store the test data for verification
|
||||||
|
List<PersistenceTestItem> testDataList = new ArrayList<>();
|
||||||
|
|
||||||
|
// Prepare a random number generator
|
||||||
|
Random random = new Random();
|
||||||
|
|
||||||
|
// Prepare the start date
|
||||||
|
ZonedDateTime startDate = ZonedDateTime.now();
|
||||||
|
|
||||||
|
// Iterate over the 50 days
|
||||||
|
for (int day = 0; day < 50; day++) {
|
||||||
|
// Calculate the current date
|
||||||
|
ZonedDateTime currentDate = startDate.plusDays(day);
|
||||||
|
|
||||||
|
// Generate a random number of values for each item
|
||||||
|
for (String itemName : itemNames) {
|
||||||
|
int numValues = 2 + random.nextInt(4); // Random number between 2 and 5
|
||||||
|
|
||||||
|
for (int valueIndex = 0; valueIndex < numValues; valueIndex++) {
|
||||||
|
// Generate a random value between 0.0 and 10.0
|
||||||
|
double value = 10.0 * random.nextDouble();
|
||||||
|
|
||||||
|
// Create the item
|
||||||
|
Item item = DataCreationHelper.createNumberItem(itemName, value);
|
||||||
|
|
||||||
|
// Store the data
|
||||||
|
service.store(item, currentDate, new DecimalType(value));
|
||||||
|
|
||||||
|
// Add the data to the test data list for verification
|
||||||
|
testDataList.add(new PersistenceTestItem(itemName, currentDate, value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return testDataList;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,105 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2024 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.net.InetSocketAddress;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.testcontainers.containers.MongoDBContainer;
|
||||||
|
|
||||||
|
import de.bwaldvogel.mongo.MongoServer;
|
||||||
|
import de.bwaldvogel.mongo.backend.memory.MemoryBackend;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class provides a container for MongoDB for testing purposes.
|
||||||
|
* It uses the Testcontainers library to manage the MongoDB container.
|
||||||
|
* It also provides an in-memory MongoDB server for testing.
|
||||||
|
*
|
||||||
|
* @author René Ulbricht - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class DatabaseTestContainer {
|
||||||
|
// A map to store MongoDBContainer instances for different MongoDB versions.
|
||||||
|
private static final Map<String, MongoDBContainer> mongoDBContainers = new HashMap<>();
|
||||||
|
|
||||||
|
// The MongoDBContainer instance for this DatabaseTestContainer.
|
||||||
|
private @Nullable MongoDBContainer mongoDBContainer;
|
||||||
|
|
||||||
|
// The MongoServer instance for this DatabaseTestContainer.
|
||||||
|
private @Nullable MongoServer server;
|
||||||
|
|
||||||
|
// The InetSocketAddress instance for this DatabaseTestContainer.
|
||||||
|
private @Nullable InetSocketAddress serverAddress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new DatabaseTestContainer for a given MongoDB version.
|
||||||
|
* If a MongoDBContainer for the given version already exists, it is reused.
|
||||||
|
*
|
||||||
|
* @param mongoDBVersion The version of MongoDB to use.
|
||||||
|
*/
|
||||||
|
public DatabaseTestContainer(String mongoDBVersion) {
|
||||||
|
server = null;
|
||||||
|
serverAddress = null;
|
||||||
|
mongoDBContainer = mongoDBContainers.computeIfAbsent(mongoDBVersion, MongoDBContainer::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new DatabaseTestContainer for an in-memory MongoDB server.
|
||||||
|
*/
|
||||||
|
public DatabaseTestContainer(MemoryBackend memoryBackend) {
|
||||||
|
mongoDBContainer = null;
|
||||||
|
server = new MongoServer(memoryBackend);
|
||||||
|
if (server != null) {
|
||||||
|
serverAddress = server.bind();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the MongoDB container or the in-memory MongoDB server.
|
||||||
|
*/
|
||||||
|
public void start() {
|
||||||
|
if (mongoDBContainer != null && !mongoDBContainer.isRunning()) {
|
||||||
|
mongoDBContainer.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Don't do anything.
|
||||||
|
*/
|
||||||
|
public void stop() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the connection string for connecting to the MongoDB container or the in-memory MongoDB server.
|
||||||
|
*
|
||||||
|
* @return The connection string.
|
||||||
|
*/
|
||||||
|
public String getConnectionString() {
|
||||||
|
@Nullable
|
||||||
|
MongoDBContainer lc_mongoDBContainer = this.mongoDBContainer;
|
||||||
|
@Nullable
|
||||||
|
InetSocketAddress lc_serverAddress = this.serverAddress;
|
||||||
|
@Nullable
|
||||||
|
MongoServer lc_server = this.server;
|
||||||
|
if (lc_mongoDBContainer != null) {
|
||||||
|
return lc_mongoDBContainer.getConnectionString();
|
||||||
|
} else if (lc_server != null && lc_serverAddress != null) {
|
||||||
|
return String.format("mongodb://%s:%s", lc_serverAddress.getHostName(), lc_serverAddress.getPort());
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,992 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2024 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 static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.bson.types.ObjectId;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
import org.openhab.core.items.GenericItem;
|
||||||
|
import org.openhab.core.items.ItemNotFoundException;
|
||||||
|
import org.openhab.core.library.items.ColorItem;
|
||||||
|
import org.openhab.core.library.items.DateTimeItem;
|
||||||
|
import org.openhab.core.library.items.ImageItem;
|
||||||
|
import org.openhab.core.library.items.NumberItem;
|
||||||
|
import org.openhab.core.library.items.StringItem;
|
||||||
|
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.QuantityType;
|
||||||
|
import org.openhab.core.library.types.RawType;
|
||||||
|
import org.openhab.core.persistence.FilterCriteria;
|
||||||
|
import org.openhab.core.persistence.HistoricItem;
|
||||||
|
import org.osgi.framework.BundleContext;
|
||||||
|
|
||||||
|
import com.mongodb.client.MongoCollection;
|
||||||
|
import com.mongodb.client.MongoDatabase;
|
||||||
|
|
||||||
|
import ch.qos.logback.classic.Level;
|
||||||
|
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||||
|
import ch.qos.logback.core.read.ListAppender;
|
||||||
|
import de.bwaldvogel.mongo.backend.memory.MemoryBackend;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the implementation of the test for MongoDB {@link PersistenceService}.
|
||||||
|
*
|
||||||
|
* @author René Ulbricht - Initial contribution
|
||||||
|
*/
|
||||||
|
public class MongoDBPersistenceServiceTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the activate method of MongoDBPersistenceService.
|
||||||
|
*
|
||||||
|
* This test checks if the activate method correctly logs the MongoDB URL, database, and collection.
|
||||||
|
* It uses different database backends provided by the provideDatabaseBackends method.
|
||||||
|
*
|
||||||
|
* @param dbContainer The container running the MongoDB instance.
|
||||||
|
*/
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("org.openhab.persistence.mongodb.internal.DataCreationHelper#provideDatabaseBackends")
|
||||||
|
public void testActivate(DatabaseTestContainer dbContainer) {
|
||||||
|
try {
|
||||||
|
// Preparation
|
||||||
|
SetupResult setupResult = DataCreationHelper.setupMongoDB("testCollection", dbContainer);
|
||||||
|
|
||||||
|
// Set up logger
|
||||||
|
ListAppender<ILoggingEvent> listAppender = DataCreationHelper.setupLogger(MongoDBPersistenceService.class,
|
||||||
|
Level.DEBUG);
|
||||||
|
|
||||||
|
// Execution
|
||||||
|
setupResult.service.activate(setupResult.bundleContext, setupResult.config);
|
||||||
|
|
||||||
|
// Verification
|
||||||
|
List<ILoggingEvent> logsList = listAppender.list;
|
||||||
|
VerificationHelper.verifyLogMessage(logsList.get(0), "MongoDB URL " + dbContainer.getConnectionString(),
|
||||||
|
Level.DEBUG);
|
||||||
|
VerificationHelper.verifyLogMessage(logsList.get(1), "MongoDB database " + setupResult.dbname, Level.DEBUG);
|
||||||
|
VerificationHelper.verifyLogMessage(logsList.get(2), "MongoDB collection testCollection", Level.DEBUG);
|
||||||
|
} finally {
|
||||||
|
dbContainer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the deactivate method of MongoDBPersistenceService.
|
||||||
|
*
|
||||||
|
* This test checks if the deactivate method correctly logs a message when the MongoDB persistence bundle is
|
||||||
|
* stopping.
|
||||||
|
* It uses different database backends provided by the provideDatabaseBackends method.
|
||||||
|
*
|
||||||
|
* @param dbContainer The container running the MongoDB instance.
|
||||||
|
*/
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("org.openhab.persistence.mongodb.internal.DataCreationHelper#provideDatabaseBackends")
|
||||||
|
public void testDeactivate(DatabaseTestContainer dbContainer) {
|
||||||
|
try {
|
||||||
|
// Preparation
|
||||||
|
SetupResult setupResult = DataCreationHelper.setupMongoDB("testCollection", dbContainer);
|
||||||
|
|
||||||
|
setupResult.service.activate(setupResult.bundleContext, setupResult.config);
|
||||||
|
|
||||||
|
// Set up logger
|
||||||
|
ListAppender<ILoggingEvent> listAppender = DataCreationHelper.setupLogger(MongoDBPersistenceService.class,
|
||||||
|
Level.DEBUG);
|
||||||
|
|
||||||
|
// Execution
|
||||||
|
setupResult.service.deactivate(1);
|
||||||
|
|
||||||
|
// Verification
|
||||||
|
List<ILoggingEvent> logsList = listAppender.list;
|
||||||
|
VerificationHelper.verifyLogMessage(logsList.get(0),
|
||||||
|
"MongoDB persistence bundle stopping. Disconnecting from database.", Level.DEBUG);
|
||||||
|
} finally {
|
||||||
|
dbContainer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the getId method of MongoDBPersistenceService.
|
||||||
|
*
|
||||||
|
* This test checks if the getId method correctly returns the ID of the MongoDBPersistenceService, which should be
|
||||||
|
* "mongodb".
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testGetId() {
|
||||||
|
// Preparation
|
||||||
|
DatabaseTestContainer dbContainer = new DatabaseTestContainer(new MemoryBackend());
|
||||||
|
try {
|
||||||
|
SetupResult setupResult = DataCreationHelper.setupMongoDB(null, dbContainer);
|
||||||
|
MongoDBPersistenceService service = setupResult.service;
|
||||||
|
|
||||||
|
// Execution
|
||||||
|
String id = service.getId();
|
||||||
|
|
||||||
|
// Verification
|
||||||
|
assertEquals("mongodb", id);
|
||||||
|
} finally {
|
||||||
|
dbContainer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the getLabel method of MongoDBPersistenceService.
|
||||||
|
*
|
||||||
|
* This test checks if the getLabel method correctly returns the label of the MongoDBPersistenceService, which
|
||||||
|
* should be "MongoDB".
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testGetLabel() {
|
||||||
|
// Preparation
|
||||||
|
DatabaseTestContainer dbContainer = new DatabaseTestContainer(new MemoryBackend());
|
||||||
|
try {
|
||||||
|
SetupResult setupResult = DataCreationHelper.setupMongoDB(null, dbContainer);
|
||||||
|
MongoDBPersistenceService service = setupResult.service;
|
||||||
|
|
||||||
|
// Execution
|
||||||
|
String label = service.getLabel(null);
|
||||||
|
|
||||||
|
// Verification
|
||||||
|
assertEquals("MongoDB", label);
|
||||||
|
} finally {
|
||||||
|
dbContainer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the store method of MongoDBPersistenceService with a NumberItem.
|
||||||
|
*
|
||||||
|
* This test checks if the store method correctly stores a NumberItem in the MongoDB database.
|
||||||
|
* It uses different database backends provided by the provideDatabaseBackends method.
|
||||||
|
*
|
||||||
|
* @param dbContainer The container running the MongoDB instance.
|
||||||
|
*/
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("org.openhab.persistence.mongodb.internal.DataCreationHelper#provideDatabaseBackends")
|
||||||
|
public void testStoreNumber(DatabaseTestContainer dbContainer) {
|
||||||
|
try {
|
||||||
|
// Preparation
|
||||||
|
SetupResult setupResult = DataCreationHelper.setupMongoDB("testCollection", dbContainer);
|
||||||
|
MongoDBPersistenceService service = setupResult.service;
|
||||||
|
MongoDatabase database = setupResult.database;
|
||||||
|
|
||||||
|
service.activate(setupResult.bundleContext, setupResult.config);
|
||||||
|
|
||||||
|
NumberItem item = DataCreationHelper.createNumberItem("TestItem", 10.1);
|
||||||
|
|
||||||
|
// Execution
|
||||||
|
service.store(item, null);
|
||||||
|
|
||||||
|
// Verification
|
||||||
|
MongoCollection<Document> collection = database.getCollection("testCollection");
|
||||||
|
List<Document> documents = (ArrayList<Document>) collection.find().into(new ArrayList<>());
|
||||||
|
|
||||||
|
assertEquals(1, documents.size()); // Assert that there is only one document
|
||||||
|
|
||||||
|
Document insertedDocument = documents.get(0); // Get the first (and only) document
|
||||||
|
|
||||||
|
VerificationHelper.verifyDocument(insertedDocument, "TestItem", 10.1);
|
||||||
|
} finally {
|
||||||
|
dbContainer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the store method of MongoDBPersistenceService with a StringItem.
|
||||||
|
*
|
||||||
|
* This test checks if the store method correctly stores a StringItem in the MongoDB database.
|
||||||
|
* It uses different database backends provided by the provideDatabaseBackends method.
|
||||||
|
*
|
||||||
|
* @param dbContainer The container running the MongoDB instance.
|
||||||
|
*/
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("org.openhab.persistence.mongodb.internal.DataCreationHelper#provideDatabaseBackends")
|
||||||
|
public void testStoreString(DatabaseTestContainer dbContainer) {
|
||||||
|
try {
|
||||||
|
// Preparation
|
||||||
|
SetupResult setupResult = DataCreationHelper.setupMongoDB("testCollection", dbContainer);
|
||||||
|
MongoDBPersistenceService service = setupResult.service;
|
||||||
|
MongoDatabase database = setupResult.database;
|
||||||
|
|
||||||
|
service.activate(setupResult.bundleContext, setupResult.config);
|
||||||
|
|
||||||
|
StringItem item = DataCreationHelper.createStringItem("TestItem", "TestValue");
|
||||||
|
|
||||||
|
// Execution
|
||||||
|
service.store(item, null);
|
||||||
|
|
||||||
|
// Verification
|
||||||
|
MongoCollection<Document> collection = database.getCollection("testCollection");
|
||||||
|
List<Document> documents = (ArrayList<Document>) collection.find().into(new ArrayList<>());
|
||||||
|
|
||||||
|
assertEquals(1, documents.size()); // Assert that there is only one document
|
||||||
|
|
||||||
|
Document insertedDocument = documents.get(0); // Get the first (and only) document
|
||||||
|
|
||||||
|
VerificationHelper.verifyDocument(insertedDocument, "TestItem", "TestValue");
|
||||||
|
} finally {
|
||||||
|
dbContainer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the store method of MongoDBPersistenceService with multiple items in a single collection.
|
||||||
|
*
|
||||||
|
* This test checks if the store method correctly stores multiple items in the same MongoDB collection.
|
||||||
|
* It uses different database backends provided by the provideDatabaseBackends method.
|
||||||
|
*
|
||||||
|
* @param dbContainer The container running the MongoDB instance.
|
||||||
|
*/
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("org.openhab.persistence.mongodb.internal.DataCreationHelper#provideDatabaseBackends")
|
||||||
|
public void testStoreSingleCollection(DatabaseTestContainer dbContainer) {
|
||||||
|
try {
|
||||||
|
// Preparation
|
||||||
|
SetupResult setupResult = DataCreationHelper.setupMongoDB("testCollection", dbContainer);
|
||||||
|
MongoDBPersistenceService service = setupResult.service;
|
||||||
|
MongoDatabase database = setupResult.database;
|
||||||
|
|
||||||
|
service.activate(setupResult.bundleContext, setupResult.config);
|
||||||
|
|
||||||
|
StringItem strItem1 = DataCreationHelper.createStringItem("TestItem", "TestValue");
|
||||||
|
StringItem strItem2 = DataCreationHelper.createStringItem("SecondTestItem", "SecondTestValue");
|
||||||
|
|
||||||
|
// Execution
|
||||||
|
service.store(strItem1, null);
|
||||||
|
service.store(strItem2, null);
|
||||||
|
|
||||||
|
// Verification
|
||||||
|
MongoCollection<Document> collection = database.getCollection("testCollection");
|
||||||
|
List<Document> documents = (ArrayList<Document>) collection.find().into(new ArrayList<>());
|
||||||
|
|
||||||
|
assertEquals(2, documents.size()); // Assert that there are two documents
|
||||||
|
|
||||||
|
Document insertedDocument1 = documents.get(0); // Get the first document
|
||||||
|
VerificationHelper.verifyDocument(insertedDocument1, "TestItem", "TestValue");
|
||||||
|
|
||||||
|
Document insertedDocument2 = documents.get(1); // Get the second document
|
||||||
|
VerificationHelper.verifyDocument(insertedDocument2, "SecondTestItem", "SecondTestValue");
|
||||||
|
} finally {
|
||||||
|
dbContainer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the store method of MongoDBPersistenceService with multiple items.
|
||||||
|
*
|
||||||
|
* This test checks if the store method correctly stores multiple items in the same MongoDB collection.
|
||||||
|
* It uses different database backends provided by the provideDatabaseBackends method.
|
||||||
|
*
|
||||||
|
* @param dbContainer The container running the MongoDB instance.
|
||||||
|
*/
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("org.openhab.persistence.mongodb.internal.DataCreationHelper#provideDatabaseBackends")
|
||||||
|
public void testStoreMultipleItemsSingleCollection(DatabaseTestContainer dbContainer) {
|
||||||
|
try {
|
||||||
|
// Preparation
|
||||||
|
SetupResult setupResult = DataCreationHelper.setupMongoDB("testCollection", dbContainer);
|
||||||
|
MongoDBPersistenceService service = setupResult.service;
|
||||||
|
MongoDatabase database = setupResult.database;
|
||||||
|
|
||||||
|
service.activate(setupResult.bundleContext, setupResult.config);
|
||||||
|
|
||||||
|
StringItem strItem1 = DataCreationHelper.createStringItem("TestItem1", "TestValue1");
|
||||||
|
StringItem strItem2 = DataCreationHelper.createStringItem("TestItem2", "TestValue2");
|
||||||
|
|
||||||
|
// Execution
|
||||||
|
service.store(strItem1, null);
|
||||||
|
service.store(strItem2, null);
|
||||||
|
|
||||||
|
// Verification
|
||||||
|
MongoCollection<Document> collection = database.getCollection("testCollection");
|
||||||
|
List<Document> documents = (ArrayList<Document>) collection.find().into(new ArrayList<>());
|
||||||
|
|
||||||
|
assertEquals(2, documents.size()); // Assert that there are two documents
|
||||||
|
|
||||||
|
Document insertedDocument1 = documents.get(0); // Get the first document
|
||||||
|
VerificationHelper.verifyDocument(insertedDocument1, "TestItem1", "TestValue1");
|
||||||
|
|
||||||
|
Document insertedDocument2 = documents.get(1); // Get the second document
|
||||||
|
VerificationHelper.verifyDocument(insertedDocument2, "TestItem2", "TestValue2");
|
||||||
|
} finally {
|
||||||
|
dbContainer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the store method of MongoDBPersistenceService with a StringItem and an alias.
|
||||||
|
*
|
||||||
|
* This test checks if the store method correctly stores a StringItem with an alias in the MongoDB database.
|
||||||
|
* It uses different database backends provided by the provideDatabaseBackends method.
|
||||||
|
*
|
||||||
|
* @param dbContainer The container running the MongoDB instance.
|
||||||
|
*/
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("org.openhab.persistence.mongodb.internal.DataCreationHelper#provideDatabaseBackends")
|
||||||
|
public void testStoreStringWithAlias(DatabaseTestContainer dbContainer) {
|
||||||
|
try {
|
||||||
|
// Preparation
|
||||||
|
SetupResult setupResult = DataCreationHelper.setupMongoDB("testCollection", dbContainer);
|
||||||
|
MongoDBPersistenceService service = setupResult.service;
|
||||||
|
MongoDatabase database = setupResult.database;
|
||||||
|
|
||||||
|
service.activate(setupResult.bundleContext, setupResult.config);
|
||||||
|
|
||||||
|
StringItem item = DataCreationHelper.createStringItem("TestItem", "TestValue");
|
||||||
|
|
||||||
|
// Execution
|
||||||
|
service.store(item, "AliasName");
|
||||||
|
|
||||||
|
// Verification
|
||||||
|
MongoCollection<Document> collection = database.getCollection("testCollection");
|
||||||
|
List<Document> documents = (ArrayList<Document>) collection.find().into(new ArrayList<>());
|
||||||
|
|
||||||
|
assertEquals(1, documents.size()); // Assert that there is only one document
|
||||||
|
|
||||||
|
Document insertedDocument = documents.get(0); // Get the first (and only) document
|
||||||
|
|
||||||
|
VerificationHelper.verifyDocumentWithAlias(insertedDocument, "AliasName", "TestItem", "TestValue");
|
||||||
|
} finally {
|
||||||
|
dbContainer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the query method of MongoDBPersistenceService with NumberItems in a single collection.
|
||||||
|
*
|
||||||
|
* This test checks if the query method correctly retrieves NumberItems from a single MongoDB collection.
|
||||||
|
* It uses different database backends provided by the provideDatabaseBackends method.
|
||||||
|
*
|
||||||
|
* @param dbContainer The container running the MongoDB instance.
|
||||||
|
*/
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("org.openhab.persistence.mongodb.internal.DataCreationHelper#provideDatabaseBackends")
|
||||||
|
public void testQueryNumberItemsInOneCollection(DatabaseTestContainer dbContainer) {
|
||||||
|
try {
|
||||||
|
// Preparation
|
||||||
|
SetupResult setupResult = DataCreationHelper.setupMongoDB("testCollection", dbContainer);
|
||||||
|
MongoDBPersistenceService service = setupResult.service;
|
||||||
|
|
||||||
|
// Add items to the ItemRegistry
|
||||||
|
NumberItem itemReg1 = DataCreationHelper.createNumberItem("TestItem", 0);
|
||||||
|
NumberItem itemReg2 = DataCreationHelper.createNumberItem("TestItem2", 0);
|
||||||
|
try {
|
||||||
|
Mockito.when(setupResult.itemRegistry.getItem("TestItem")).thenReturn(itemReg1);
|
||||||
|
Mockito.when(setupResult.itemRegistry.getItem("TestItem2")).thenReturn(itemReg2);
|
||||||
|
} catch (ItemNotFoundException e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
service.activate(setupResult.bundleContext, setupResult.config);
|
||||||
|
|
||||||
|
// Store some items
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
NumberItem item1 = DataCreationHelper.createNumberItem("TestItem", i);
|
||||||
|
NumberItem item2 = DataCreationHelper.createNumberItem("TestItem2", i * 2);
|
||||||
|
service.store(item1, null);
|
||||||
|
service.store(item2, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execution
|
||||||
|
FilterCriteria filter1 = DataCreationHelper.createFilterCriteria("TestItem");
|
||||||
|
Iterable<HistoricItem> result1 = service.query(filter1);
|
||||||
|
|
||||||
|
FilterCriteria filter2 = DataCreationHelper.createFilterCriteria("TestItem2");
|
||||||
|
Iterable<HistoricItem> result2 = service.query(filter2);
|
||||||
|
|
||||||
|
// Verification
|
||||||
|
VerificationHelper.verifyQueryResult(result1, 0, 1, 10);
|
||||||
|
VerificationHelper.verifyQueryResult(result2, 0, 2, 10);
|
||||||
|
} finally {
|
||||||
|
dbContainer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the query method of MongoDBPersistenceService with NumberItems in multiple collections.
|
||||||
|
*
|
||||||
|
* This test checks if the query method correctly retrieves NumberItems from multiple MongoDB collections.
|
||||||
|
* It uses different database backends provided by the provideDatabaseBackends method.
|
||||||
|
*
|
||||||
|
* @param dbContainer The container running the MongoDB instance.
|
||||||
|
*/
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("org.openhab.persistence.mongodb.internal.DataCreationHelper#provideDatabaseBackends")
|
||||||
|
public void testQueryNumberItemsInMultipleCollections(DatabaseTestContainer dbContainer) {
|
||||||
|
try {
|
||||||
|
// Preparation
|
||||||
|
SetupResult setupResult = DataCreationHelper.setupMongoDB(null, dbContainer);
|
||||||
|
MongoDBPersistenceService service = setupResult.service;
|
||||||
|
BundleContext bundleContext = setupResult.bundleContext;
|
||||||
|
Map<String, Object> config = setupResult.config;
|
||||||
|
|
||||||
|
try {
|
||||||
|
Mockito.when(setupResult.itemRegistry.getItem("TestItem"))
|
||||||
|
.thenReturn(DataCreationHelper.createNumberItem("TestItem", 0));
|
||||||
|
Mockito.when(setupResult.itemRegistry.getItem("TestItem2"))
|
||||||
|
.thenReturn(DataCreationHelper.createNumberItem("TestItem2", 0));
|
||||||
|
} catch (ItemNotFoundException e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
service.activate(bundleContext, config);
|
||||||
|
|
||||||
|
// Store some items
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
NumberItem item1 = DataCreationHelper.createNumberItem("TestItem", i);
|
||||||
|
NumberItem item2 = DataCreationHelper.createNumberItem("TestItem2", i * 2);
|
||||||
|
service.store(item1, null);
|
||||||
|
service.store(item2, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execution
|
||||||
|
FilterCriteria filter1 = DataCreationHelper.createFilterCriteria("TestItem");
|
||||||
|
Iterable<HistoricItem> result1 = service.query(filter1);
|
||||||
|
|
||||||
|
FilterCriteria filter2 = DataCreationHelper.createFilterCriteria("TestItem2");
|
||||||
|
Iterable<HistoricItem> result2 = service.query(filter2);
|
||||||
|
|
||||||
|
// Verification
|
||||||
|
VerificationHelper.verifyQueryResult(result1, 0, 1, 10);
|
||||||
|
VerificationHelper.verifyQueryResult(result2, 0, 2, 10);
|
||||||
|
} finally {
|
||||||
|
dbContainer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the query method of MongoDBPersistenceService with NumberItems in a single collection and a time range.
|
||||||
|
*
|
||||||
|
* This test checks if the query method correctly retrieves NumberItems from a single MongoDB collection within a
|
||||||
|
* specified time range.
|
||||||
|
* It uses different database backends provided by the provideDatabaseBackends method.
|
||||||
|
*
|
||||||
|
* @param dbContainer The container running the MongoDB instance.
|
||||||
|
*/
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("org.openhab.persistence.mongodb.internal.DataCreationHelper#provideDatabaseBackends")
|
||||||
|
public void testQueryNumberItemsInOneCollectionTimeRange(DatabaseTestContainer dbContainer) {
|
||||||
|
try {
|
||||||
|
// Preparation
|
||||||
|
SetupResult setupResult = DataCreationHelper.setupMongoDB("testCollection", dbContainer);
|
||||||
|
MongoDBPersistenceService service = setupResult.service;
|
||||||
|
MongoDatabase database = setupResult.database;
|
||||||
|
|
||||||
|
try {
|
||||||
|
Mockito.when(setupResult.itemRegistry.getItem("TestItem"))
|
||||||
|
.thenReturn(DataCreationHelper.createNumberItem("TestItem", 0));
|
||||||
|
Mockito.when(setupResult.itemRegistry.getItem("TestItem2"))
|
||||||
|
.thenReturn(DataCreationHelper.createNumberItem("TestItem2", 0));
|
||||||
|
} catch (ItemNotFoundException e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
service.activate(setupResult.bundleContext, setupResult.config);
|
||||||
|
|
||||||
|
// Get the collection
|
||||||
|
MongoCollection<Document> collection = database.getCollection("testCollection");
|
||||||
|
|
||||||
|
// Store items directly to the database with defined timestamps
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
Document obj = DataCreationHelper.createDocument("TestItem", i, LocalDate.now().minusDays(i));
|
||||||
|
collection.insertOne(obj);
|
||||||
|
|
||||||
|
Document obj2 = DataCreationHelper.createDocument("TestItem2", i * 2, LocalDate.now().minusDays(i));
|
||||||
|
collection.insertOne(obj2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execution
|
||||||
|
FilterCriteria filter1 = DataCreationHelper.createFilterCriteria("TestItem",
|
||||||
|
ZonedDateTime.now().minusDays(5), null);
|
||||||
|
Iterable<HistoricItem> result1 = service.query(filter1);
|
||||||
|
|
||||||
|
// Verification
|
||||||
|
VerificationHelper.verifyQueryResult(result1, 4, -1, 5);
|
||||||
|
} finally {
|
||||||
|
dbContainer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the query method of MongoDBPersistenceService with NumberItems in a single collection and a state equals
|
||||||
|
* filter.
|
||||||
|
*
|
||||||
|
* This test checks if the query method correctly retrieves NumberItems from a single MongoDB collection that match
|
||||||
|
* a specified state.
|
||||||
|
* It uses different database backends provided by the provideDatabaseBackends method.
|
||||||
|
*
|
||||||
|
* @param dbContainer The container running the MongoDB instance.
|
||||||
|
*/
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("org.openhab.persistence.mongodb.internal.DataCreationHelper#provideDatabaseBackends")
|
||||||
|
public void testQueryNumberItemsInOneCollectionStateEquals(DatabaseTestContainer dbContainer) {
|
||||||
|
try {
|
||||||
|
// Preparation
|
||||||
|
SetupResult setupResult = DataCreationHelper.setupMongoDB("testCollection", dbContainer);
|
||||||
|
MongoDBPersistenceService service = setupResult.service;
|
||||||
|
MongoDatabase database = setupResult.database;
|
||||||
|
|
||||||
|
try {
|
||||||
|
Mockito.when(setupResult.itemRegistry.getItem("TestItem"))
|
||||||
|
.thenReturn(DataCreationHelper.createNumberItem("TestItem", 0));
|
||||||
|
} catch (ItemNotFoundException e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
service.activate(setupResult.bundleContext, setupResult.config);
|
||||||
|
|
||||||
|
// Get the collection
|
||||||
|
MongoCollection<Document> collection = database.getCollection("testCollection");
|
||||||
|
|
||||||
|
// Store items directly to the database with defined timestamps
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
Document obj = DataCreationHelper.createDocument("TestItem", i, LocalDate.now().minusDays(i));
|
||||||
|
collection.insertOne(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
Document obj = DataCreationHelper.createDocument("TestItem", 4.0, LocalDate.now());
|
||||||
|
collection.insertOne(obj);
|
||||||
|
|
||||||
|
// Execution
|
||||||
|
FilterCriteria filter1 = DataCreationHelper.createFilterCriteria("TestItem", null, null);
|
||||||
|
filter1.setState(new DecimalType(4.0));
|
||||||
|
filter1.setOperator(FilterCriteria.Operator.EQ);
|
||||||
|
|
||||||
|
Iterable<HistoricItem> result1 = service.query(filter1);
|
||||||
|
|
||||||
|
// Verification
|
||||||
|
VerificationHelper.verifyQueryResult(result1, 4, 0, 2);
|
||||||
|
} finally {
|
||||||
|
dbContainer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the store method of the MongoDBPersistenceService with all types of openHAB items.
|
||||||
|
* Each item is stored in the collection in the MongoDB database.
|
||||||
|
*
|
||||||
|
* @param item The item to store in the database.
|
||||||
|
*/
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("org.openhab.persistence.mongodb.internal.DataCreationHelper#provideOpenhabItemTypes")
|
||||||
|
public void testStoreAllOpenhabItemTypesSingleCollection(GenericItem item) {
|
||||||
|
// Preparation
|
||||||
|
DatabaseTestContainer dbContainer = new DatabaseTestContainer(new MemoryBackend());
|
||||||
|
try {
|
||||||
|
SetupResult setupResult = DataCreationHelper.setupMongoDB("testCollection", dbContainer);
|
||||||
|
MongoDBPersistenceService service = setupResult.service;
|
||||||
|
MongoDatabase database = setupResult.database;
|
||||||
|
|
||||||
|
service.activate(setupResult.bundleContext, setupResult.config);
|
||||||
|
|
||||||
|
// Execution
|
||||||
|
service.store(item, null);
|
||||||
|
|
||||||
|
// Verification
|
||||||
|
MongoCollection<Document> collection = database.getCollection("testCollection");
|
||||||
|
List<Document> documents = (ArrayList<Document>) collection.find().into(new ArrayList<>());
|
||||||
|
|
||||||
|
assertEquals(1, documents.size()); // Assert that there is only one document
|
||||||
|
|
||||||
|
Document insertedDocument = documents.get(0); // Get the first (and only) document
|
||||||
|
|
||||||
|
VerificationHelper.verifyDocument(insertedDocument, item.getName(), item.getState());
|
||||||
|
} finally {
|
||||||
|
dbContainer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the store and query method for various image sizes of the MongoDBPersistenceService
|
||||||
|
* Each item is queried with the type from one collection in the MongoDB database.
|
||||||
|
*
|
||||||
|
* @param item The item to store in the database.
|
||||||
|
*/
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("org.openhab.persistence.mongodb.internal.DataCreationHelper#provideOpenhabImageItemsInDifferentSizes")
|
||||||
|
public void testStoreAndQueryyLargerImages(ImageItem item) {
|
||||||
|
// Preparation
|
||||||
|
DatabaseTestContainer dbContainer = new DatabaseTestContainer(new MemoryBackend());
|
||||||
|
try {
|
||||||
|
SetupResult setupResult = DataCreationHelper.setupMongoDB("testCollection", dbContainer);
|
||||||
|
MongoDBPersistenceService service = setupResult.service;
|
||||||
|
|
||||||
|
service.activate(setupResult.bundleContext, setupResult.config);
|
||||||
|
try {
|
||||||
|
Mockito.when(setupResult.itemRegistry.getItem(item.getName())).thenReturn(item);
|
||||||
|
} catch (ItemNotFoundException e) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
service.store(item, null);
|
||||||
|
} catch (org.bson.BsonMaximumSizeExceededException e) {
|
||||||
|
if (item.getName().equals("ImageItem20MB")) {
|
||||||
|
// this is expected
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execution
|
||||||
|
FilterCriteria filter = DataCreationHelper.createFilterCriteria(item.getName());
|
||||||
|
Iterable<HistoricItem> result = service.query(filter);
|
||||||
|
// Verification
|
||||||
|
|
||||||
|
VerificationHelper.verifyQueryResult(result, item.getState());
|
||||||
|
} finally {
|
||||||
|
dbContainer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the old way of storing data and query method of the MongoDBPersistenceService with all types of openHAB
|
||||||
|
* items.
|
||||||
|
* Each item is queried with the type from one collection in the MongoDB database.
|
||||||
|
*
|
||||||
|
* @param item The item to store in the database.
|
||||||
|
*/
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("org.openhab.persistence.mongodb.internal.DataCreationHelper#provideOpenhabItemTypes")
|
||||||
|
public void testOldDataQueryAllOpenhabItemTypesSingleCollection(GenericItem item) {
|
||||||
|
// Preparation
|
||||||
|
DatabaseTestContainer dbContainer = new DatabaseTestContainer(new MemoryBackend());
|
||||||
|
try {
|
||||||
|
SetupResult setupResult = DataCreationHelper.setupMongoDB("testCollection", dbContainer);
|
||||||
|
MongoDBPersistenceService service = setupResult.service;
|
||||||
|
MongoDatabase database = setupResult.database;
|
||||||
|
|
||||||
|
service.activate(setupResult.bundleContext, setupResult.config);
|
||||||
|
try {
|
||||||
|
Mockito.when(setupResult.itemRegistry.getItem(item.getName())).thenReturn(item);
|
||||||
|
} catch (ItemNotFoundException e) {
|
||||||
|
}
|
||||||
|
MongoCollection<Document> collection = database.getCollection("testCollection");
|
||||||
|
DataCreationHelper.storeOldData(collection, item.getName(), item.getState());
|
||||||
|
// after storing, we have to adjust the expected values for ImageItems, ColorItems as well as DateTimeItems
|
||||||
|
if (item instanceof ImageItem) {
|
||||||
|
item.setState(new RawType(new byte[0], "application/octet-stream"));
|
||||||
|
} else if (item instanceof ColorItem) {
|
||||||
|
item.setState(new HSBType("0,0,0"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execution
|
||||||
|
FilterCriteria filter = DataCreationHelper.createFilterCriteria(item.getName());
|
||||||
|
Iterable<HistoricItem> result = service.query(filter);
|
||||||
|
// Verification
|
||||||
|
|
||||||
|
if (item instanceof DateTimeItem) {
|
||||||
|
// verify just the date part
|
||||||
|
assertEquals(((DateTimeType) item.getState()).getZonedDateTime().toLocalDate(),
|
||||||
|
((DateTimeType) result.iterator().next().getState()).getZonedDateTime().toLocalDate());
|
||||||
|
} else {
|
||||||
|
VerificationHelper.verifyQueryResult(result, item.getState());
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
dbContainer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the writting of NumberItems including units
|
||||||
|
* Each item should be written to the database with the unit information
|
||||||
|
*
|
||||||
|
* @param item The item to store in the database.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testStoreNumberItemWithUnit() {
|
||||||
|
// Preparation
|
||||||
|
DatabaseTestContainer dbContainer = new DatabaseTestContainer(new MemoryBackend());
|
||||||
|
try {
|
||||||
|
SetupResult setupResult = DataCreationHelper.setupMongoDB("testCollection", dbContainer);
|
||||||
|
MongoDBPersistenceService service = setupResult.service;
|
||||||
|
MongoDatabase database = setupResult.database;
|
||||||
|
|
||||||
|
service.activate(setupResult.bundleContext, setupResult.config);
|
||||||
|
MongoCollection<Document> collection = database.getCollection("testCollection");
|
||||||
|
|
||||||
|
NumberItem item = DataCreationHelper.createNumberItem("Number:Energy", "TestItem",
|
||||||
|
new QuantityType<>("10.1 kWh"));
|
||||||
|
|
||||||
|
// Execution
|
||||||
|
service.store(item, null);
|
||||||
|
|
||||||
|
// Verification
|
||||||
|
List<Document> documents = (ArrayList<Document>) collection.find().into(new ArrayList<>());
|
||||||
|
|
||||||
|
assertEquals(1, documents.size()); // Assert that there is only one document
|
||||||
|
|
||||||
|
Document insertedDocument = documents.get(0); // Get the first (and only) document
|
||||||
|
|
||||||
|
assertEquals(10.1, insertedDocument.get(MongoDBFields.FIELD_VALUE));
|
||||||
|
assertEquals("kWh", insertedDocument.get(MongoDBFields.FIELD_UNIT));
|
||||||
|
} finally {
|
||||||
|
dbContainer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the reading of NumberItems including units
|
||||||
|
* Each item should be written to the database with the unit information
|
||||||
|
*
|
||||||
|
* @param item The item to store in the database.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testQueryNumberItemWithUnit() {
|
||||||
|
// Preparation
|
||||||
|
DatabaseTestContainer dbContainer = new DatabaseTestContainer(new MemoryBackend());
|
||||||
|
try {
|
||||||
|
SetupResult setupResult = DataCreationHelper.setupMongoDB("testCollection", dbContainer);
|
||||||
|
MongoDBPersistenceService service = setupResult.service;
|
||||||
|
MongoDatabase database = setupResult.database;
|
||||||
|
|
||||||
|
service.activate(setupResult.bundleContext, setupResult.config);
|
||||||
|
MongoCollection<Document> collection = database.getCollection("testCollection");
|
||||||
|
|
||||||
|
NumberItem item = DataCreationHelper.createNumberItem("Number:Energy", "TestItem",
|
||||||
|
new QuantityType<>("10.1 MWh"));
|
||||||
|
try {
|
||||||
|
Mockito.when(setupResult.itemRegistry.getItem("TestItem")).thenReturn(item);
|
||||||
|
} catch (ItemNotFoundException e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Document obj = new Document();
|
||||||
|
obj.put(MongoDBFields.FIELD_ID, new ObjectId());
|
||||||
|
obj.put(MongoDBFields.FIELD_ITEM, "TestItem");
|
||||||
|
obj.put(MongoDBFields.FIELD_REALNAME, "TestItem");
|
||||||
|
obj.put(MongoDBFields.FIELD_TIMESTAMP, new Date());
|
||||||
|
obj.put(MongoDBFields.FIELD_VALUE, 201.5);
|
||||||
|
obj.put(MongoDBFields.FIELD_UNIT, "Wh");
|
||||||
|
collection.insertOne(obj);
|
||||||
|
|
||||||
|
// Execution
|
||||||
|
FilterCriteria filter = DataCreationHelper.createFilterCriteria("TestItem");
|
||||||
|
Iterable<HistoricItem> result = service.query(filter);
|
||||||
|
VerificationHelper.verifyQueryResult(result, new QuantityType<>("201.5 Wh"));
|
||||||
|
} finally {
|
||||||
|
dbContainer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the toString of a MongoDBItem
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param item The item to store in the database.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testHistoricItemToString() {
|
||||||
|
// Preparation
|
||||||
|
ZonedDateTime now = ZonedDateTime.now();
|
||||||
|
HistoricItem item = new MongoDBItem("TestItem", new DecimalType(10.1), now);
|
||||||
|
|
||||||
|
// Execution
|
||||||
|
String result = item.toString();
|
||||||
|
|
||||||
|
// Verification
|
||||||
|
// Jan 29, 2024, 8:43:26 PM: TestItem -> 10.1
|
||||||
|
String expected = DateFormat.getDateTimeInstance().format(Date.from(now.toInstant())) + ": TestItem -> 10.1";
|
||||||
|
assertEquals(expected, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Test the store method which stores a item state as well as a timestampe (ZonedDateTime) and check the result in
|
||||||
|
* the database
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testStoreItemWithTimestamp() {
|
||||||
|
// Preparation
|
||||||
|
DatabaseTestContainer dbContainer = new DatabaseTestContainer(new MemoryBackend());
|
||||||
|
try {
|
||||||
|
SetupResult setupResult = DataCreationHelper.setupMongoDB(null, dbContainer);
|
||||||
|
MongoDBPersistenceService service = setupResult.service;
|
||||||
|
MongoDatabase database = setupResult.database;
|
||||||
|
|
||||||
|
service.activate(setupResult.bundleContext, setupResult.config);
|
||||||
|
try {
|
||||||
|
Mockito.when(setupResult.itemRegistry.getItem("TestItem"))
|
||||||
|
.thenReturn(DataCreationHelper.createNumberItem("TestItem", 0));
|
||||||
|
} catch (ItemNotFoundException e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execution
|
||||||
|
NumberItem item = DataCreationHelper.createNumberItem("TestItem", 10.1);
|
||||||
|
DecimalType historicState = new DecimalType(11110.1);
|
||||||
|
ZonedDateTime now = ZonedDateTime.now();
|
||||||
|
service.store(item, now, historicState);
|
||||||
|
|
||||||
|
// Verification
|
||||||
|
MongoCollection<Document> collection = database.getCollection("TestItem");
|
||||||
|
List<Document> documents = (ArrayList<Document>) collection.find().into(new ArrayList<>());
|
||||||
|
|
||||||
|
assertEquals(1, documents.size()); // Assert that there is only one document
|
||||||
|
|
||||||
|
Document insertedDocument = documents.get(0); // Get the first (and only) document
|
||||||
|
|
||||||
|
VerificationHelper.verifyDocument(insertedDocument, "TestItem", historicState);
|
||||||
|
assertEquals(Date.from(now.toInstant()), insertedDocument.get(MongoDBFields.FIELD_TIMESTAMP));
|
||||||
|
} finally {
|
||||||
|
dbContainer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Test the store method which stores a item state as well as a timestampe (ZonedDateTime) and check the result in
|
||||||
|
* the database
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testStoreItemWithTimestampAndAlias() {
|
||||||
|
// Preparation
|
||||||
|
DatabaseTestContainer dbContainer = new DatabaseTestContainer(new MemoryBackend());
|
||||||
|
try {
|
||||||
|
SetupResult setupResult = DataCreationHelper.setupMongoDB(null, dbContainer);
|
||||||
|
MongoDBPersistenceService service = setupResult.service;
|
||||||
|
MongoDatabase database = setupResult.database;
|
||||||
|
|
||||||
|
service.activate(setupResult.bundleContext, setupResult.config);
|
||||||
|
try {
|
||||||
|
Mockito.when(setupResult.itemRegistry.getItem("TestItem"))
|
||||||
|
.thenReturn(DataCreationHelper.createNumberItem("TestItem", 0));
|
||||||
|
} catch (ItemNotFoundException e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execution
|
||||||
|
NumberItem item = DataCreationHelper.createNumberItem("TestItem", 10.1);
|
||||||
|
DecimalType historicState = new DecimalType(11110.1);
|
||||||
|
ZonedDateTime now = ZonedDateTime.now();
|
||||||
|
service.store(item, now, historicState, "AliasName");
|
||||||
|
|
||||||
|
// Verification
|
||||||
|
MongoCollection<Document> collection = database.getCollection("TestItem");
|
||||||
|
List<Document> documents = (ArrayList<Document>) collection.find().into(new ArrayList<>());
|
||||||
|
|
||||||
|
assertEquals(1, documents.size()); // Assert that there is only one document
|
||||||
|
|
||||||
|
Document insertedDocument = documents.get(0); // Get the first (and only) document
|
||||||
|
|
||||||
|
VerificationHelper.verifyDocumentWithAlias(insertedDocument, "AliasName", "TestItem", historicState);
|
||||||
|
assertEquals(Date.from(now.toInstant()), insertedDocument.get(MongoDBFields.FIELD_TIMESTAMP));
|
||||||
|
} finally {
|
||||||
|
dbContainer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Test the remove method to remove one item from the database
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testremoveOneItem() {
|
||||||
|
// Preparation
|
||||||
|
DatabaseTestContainer dbContainer = new DatabaseTestContainer(new MemoryBackend());
|
||||||
|
try {
|
||||||
|
SetupResult setupResult = DataCreationHelper.setupMongoDB("testcollection", dbContainer);
|
||||||
|
MongoDBPersistenceService service = setupResult.service;
|
||||||
|
MongoDatabase database = setupResult.database;
|
||||||
|
|
||||||
|
service.activate(setupResult.bundleContext, setupResult.config);
|
||||||
|
|
||||||
|
for (double i = 0; i < 10.00; i += 0.3) {
|
||||||
|
service.store(DataCreationHelper.createNumberItem("TestItem", i));
|
||||||
|
}
|
||||||
|
service.store(DataCreationHelper.createNumberItem("TestItemOther", 10.1));
|
||||||
|
|
||||||
|
// Execution
|
||||||
|
service.remove(DataCreationHelper.createFilterCriteria("TestItem", null, null));
|
||||||
|
|
||||||
|
// Verification
|
||||||
|
MongoCollection<Document> collection = database.getCollection("testcollection");
|
||||||
|
|
||||||
|
List<Document> documents = (ArrayList<Document>) collection.find().into(new ArrayList<>());
|
||||||
|
|
||||||
|
assertEquals(1, documents.size()); // Assert that there is the other document
|
||||||
|
|
||||||
|
VerificationHelper.verifyDocument(documents.get(0), "TestItemOther", 10.1);
|
||||||
|
} finally {
|
||||||
|
dbContainer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Test the remove method to remove values of a given timerange for one item
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testremoveATimeRangeFromOneItem() {
|
||||||
|
// Preparation
|
||||||
|
DatabaseTestContainer dbContainer = new DatabaseTestContainer(new MemoryBackend());
|
||||||
|
try {
|
||||||
|
SetupResult setupResult = DataCreationHelper.setupMongoDB("testcollection", dbContainer);
|
||||||
|
MongoDBPersistenceService service = setupResult.service;
|
||||||
|
MongoDatabase database = setupResult.database;
|
||||||
|
|
||||||
|
service.activate(setupResult.bundleContext, setupResult.config);
|
||||||
|
|
||||||
|
List<PersistenceTestItem> testDataList = DataCreationHelper.createTestData(service, "TestItem",
|
||||||
|
"TestItemOther");
|
||||||
|
|
||||||
|
// Execution
|
||||||
|
// Calculate the start and end dates
|
||||||
|
ZonedDateTime startDate = ZonedDateTime.now().plusDays(3).truncatedTo(ChronoUnit.DAYS);
|
||||||
|
ZonedDateTime endDate = ZonedDateTime.now().plusDays(17).truncatedTo(ChronoUnit.DAYS).plusDays(1)
|
||||||
|
.minusNanos(1);
|
||||||
|
|
||||||
|
// Create the filter and remove the data
|
||||||
|
service.remove(DataCreationHelper.createFilterCriteria("TestItem", startDate, endDate));
|
||||||
|
|
||||||
|
// Verification
|
||||||
|
MongoCollection<Document> collection = database.getCollection("testcollection");
|
||||||
|
|
||||||
|
// Query the database for all data points
|
||||||
|
List<Document> documents = (ArrayList<Document>) collection.find().into(new ArrayList<>());
|
||||||
|
|
||||||
|
// Create a set of the returned data points
|
||||||
|
Set<PersistenceTestItem> returnedData = documents.stream()
|
||||||
|
.map(doc -> new PersistenceTestItem(doc.getString(MongoDBFields.FIELD_ITEM),
|
||||||
|
ZonedDateTime.ofInstant(doc.getDate(MongoDBFields.FIELD_TIMESTAMP).toInstant(),
|
||||||
|
ZoneId.systemDefault()),
|
||||||
|
doc.getDouble(MongoDBFields.FIELD_VALUE)))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
// Create a set of the expected data points
|
||||||
|
Set<PersistenceTestItem> expectedData = testDataList
|
||||||
|
.stream().filter(testData -> !(testData.itemName.equals("TestItem")
|
||||||
|
&& testData.date.isAfter(startDate) && testData.date.isBefore(endDate)))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
for (PersistenceTestItem expectedItem : expectedData) {
|
||||||
|
// Assert that this item is in the returned data
|
||||||
|
assertTrue(returnedData.contains(expectedItem),
|
||||||
|
"Expected item not found in returned data: " + expectedItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate over the returned data
|
||||||
|
for (PersistenceTestItem returnedItem : returnedData) {
|
||||||
|
// Assert that this item is in the expected data
|
||||||
|
assertTrue(expectedData.contains(returnedItem),
|
||||||
|
"Unexpected item found in returned data: " + returnedItem);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
dbContainer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2024 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.time.ZonedDateTime;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class provides helper methods to store generated data for persistence tests.
|
||||||
|
*
|
||||||
|
* @author René Ulbricht - Initial contribution
|
||||||
|
*/
|
||||||
|
public class PersistenceTestItem {
|
||||||
|
public final String itemName;
|
||||||
|
public final ZonedDateTime date;
|
||||||
|
public final double value;
|
||||||
|
|
||||||
|
public PersistenceTestItem(String itemName, ZonedDateTime date, double value) {
|
||||||
|
this.itemName = itemName;
|
||||||
|
this.date = date.truncatedTo(ChronoUnit.MILLIS);
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "PersistenceTestItem{" + "item='" + itemName + '\'' + ", date=" + date + ", value=" + value + '}';
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(itemName, date, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj == null || getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
PersistenceTestItem other = (PersistenceTestItem) obj;
|
||||||
|
return other.itemName.equals(itemName) && other.date.equals(date) && other.value == value;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2024 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.util.Map;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.openhab.core.items.ItemRegistry;
|
||||||
|
import org.osgi.framework.BundleContext;
|
||||||
|
|
||||||
|
import com.mongodb.client.MongoDatabase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class provides helper methods to create test items.
|
||||||
|
*
|
||||||
|
* @author René Ulbricht - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class SetupResult {
|
||||||
|
public MongoDBPersistenceService service;
|
||||||
|
public MongoDatabase database;
|
||||||
|
public BundleContext bundleContext;
|
||||||
|
public Map<String, Object> config;
|
||||||
|
public ItemRegistry itemRegistry;
|
||||||
|
public String dbname;
|
||||||
|
|
||||||
|
public SetupResult(MongoDBPersistenceService service, MongoDatabase database, BundleContext bundleContext,
|
||||||
|
Map<String, Object> config, ItemRegistry itemRegistry, String dbname) {
|
||||||
|
this.service = service;
|
||||||
|
this.database = database;
|
||||||
|
this.dbname = dbname;
|
||||||
|
this.bundleContext = bundleContext;
|
||||||
|
this.config = config;
|
||||||
|
this.itemRegistry = itemRegistry;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,206 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2024 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 static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
|
import org.bson.Document;
|
||||||
|
import org.bson.json.JsonWriterSettings;
|
||||||
|
import org.bson.types.Binary;
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
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.IncreaseDecreaseType;
|
||||||
|
import org.openhab.core.library.types.NextPreviousType;
|
||||||
|
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.PlayPauseType;
|
||||||
|
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.RewindFastforwardType;
|
||||||
|
import org.openhab.core.library.types.StopMoveType;
|
||||||
|
import org.openhab.core.library.types.StringListType;
|
||||||
|
import org.openhab.core.library.types.StringType;
|
||||||
|
import org.openhab.core.library.types.UpDownType;
|
||||||
|
import org.openhab.core.persistence.HistoricItem;
|
||||||
|
|
||||||
|
import ch.qos.logback.classic.Level;
|
||||||
|
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a helper class for verifying various aspects of the MongoDB persistence service.
|
||||||
|
* It provides methods for verifying log messages, MongoDB documents, and query results.
|
||||||
|
* Each verification method checks if the actual value matches the expected value and throws an
|
||||||
|
* AssertionError if they do not match.
|
||||||
|
*
|
||||||
|
* @author René Ulbricht - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class VerificationHelper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies a log message.
|
||||||
|
*
|
||||||
|
* @param logEvent The log event to verify.
|
||||||
|
* @param expectedMessage The expected message of the log event.
|
||||||
|
* @param expectedLevel The expected level of the log event.
|
||||||
|
*/
|
||||||
|
public static void verifyLogMessage(ILoggingEvent logEvent, String expectedMessage, Level expectedLevel) {
|
||||||
|
assertEquals(expectedMessage, logEvent.getFormattedMessage());
|
||||||
|
assertEquals(expectedLevel, logEvent.getLevel());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies a document.
|
||||||
|
*
|
||||||
|
* @param document The document to verify.
|
||||||
|
* @param expectedItem The expected item of the document.
|
||||||
|
* @param expectedValue The expected value of the document.
|
||||||
|
*/
|
||||||
|
public static void verifyDocument(Document document, String expectedItem, Object expectedValue) {
|
||||||
|
verifyDocumentWithAlias(document, expectedItem, expectedItem, expectedValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies a document with an alias.
|
||||||
|
*
|
||||||
|
* @param document The document to verify.
|
||||||
|
* @param expectedAlias The expected alias of the document.
|
||||||
|
* @param expectedRealName The expected real name of the document.
|
||||||
|
* @param expectedValue The expected value of the document. Can be a String or a Double.
|
||||||
|
*/
|
||||||
|
public static void verifyDocumentWithAlias(Document document, String expectedAlias, String expectedRealName,
|
||||||
|
Object expectedValue) {
|
||||||
|
assertEquals(expectedAlias, document.get(MongoDBFields.FIELD_ITEM));
|
||||||
|
assertEquals(expectedRealName, document.get(MongoDBFields.FIELD_REALNAME));
|
||||||
|
|
||||||
|
// Use the map to handle the expected value
|
||||||
|
BiFunction<Object, Document, Pair<Object, Object>> handler = HandleTypes.get(expectedValue.getClass());
|
||||||
|
if (handler == null) {
|
||||||
|
throw new IllegalArgumentException("Unsupported type: " + expectedValue.getClass());
|
||||||
|
}
|
||||||
|
Pair<Object, Object> values = handler.apply(expectedValue, document);
|
||||||
|
|
||||||
|
JsonWriterSettings jsonWriterSettings = JsonWriterSettings.builder().indent(true).build();
|
||||||
|
assertEquals(values.getLeft(), values.getRight(),
|
||||||
|
"Document: (" + expectedValue.getClass().getSimpleName() + ") " + document.toJson(jsonWriterSettings));
|
||||||
|
|
||||||
|
assertNotNull(document.get("_id"));
|
||||||
|
assertNotNull(document.get("timestamp"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies the result of a query.
|
||||||
|
*
|
||||||
|
* @param result The result of the query.
|
||||||
|
* @param startState The state of the first item in the result.
|
||||||
|
* @param increment The increment for the expected state.
|
||||||
|
*/
|
||||||
|
public static void verifyQueryResult(Iterable<HistoricItem> result, int startState, int increment, int totalSize) {
|
||||||
|
List<HistoricItem> resultList = new ArrayList<>();
|
||||||
|
result.forEach(resultList::add);
|
||||||
|
|
||||||
|
assertEquals(totalSize, resultList.size());
|
||||||
|
|
||||||
|
int expectedState = startState;
|
||||||
|
for (HistoricItem item : resultList) {
|
||||||
|
assertEquals(expectedState, ((DecimalType) item.getState()).intValue());
|
||||||
|
expectedState += increment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void verifyQueryResult(Iterable<HistoricItem> result, Object expectedState) {
|
||||||
|
List<HistoricItem> resultList = new ArrayList<>();
|
||||||
|
result.forEach(resultList::add);
|
||||||
|
|
||||||
|
assertEquals(1, resultList.size());
|
||||||
|
|
||||||
|
assertEquals(expectedState, resultList.get(0).getState());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define a map from types to functions that handle those types
|
||||||
|
private static final Map<Class<?>, BiFunction<Object, Document, Pair<Object, Object>>> HandleTypes = Map.ofEntries(
|
||||||
|
Map.entry(Double.class, VerificationHelper::handleGeneric),
|
||||||
|
Map.entry(String.class, VerificationHelper::handleGeneric),
|
||||||
|
Map.entry(HSBType.class, VerificationHelper::handleToString),
|
||||||
|
Map.entry(DecimalType.class, VerificationHelper::handleDecimalType),
|
||||||
|
Map.entry(DateTimeType.class, VerificationHelper::handleDateTimeType),
|
||||||
|
Map.entry(IncreaseDecreaseType.class, VerificationHelper::handleToString),
|
||||||
|
Map.entry(RewindFastforwardType.class, VerificationHelper::handleToString),
|
||||||
|
Map.entry(NextPreviousType.class, VerificationHelper::handleToString),
|
||||||
|
Map.entry(OnOffType.class, VerificationHelper::handleToString),
|
||||||
|
Map.entry(OpenClosedType.class, VerificationHelper::handleToString),
|
||||||
|
Map.entry(PercentType.class, VerificationHelper::handlePercentType),
|
||||||
|
Map.entry(PlayPauseType.class, VerificationHelper::handleToString),
|
||||||
|
Map.entry(PointType.class, VerificationHelper::handleToString),
|
||||||
|
Map.entry(StopMoveType.class, VerificationHelper::handleToString),
|
||||||
|
Map.entry(StringListType.class, VerificationHelper::handleToString),
|
||||||
|
Map.entry(StringType.class, VerificationHelper::handleGeneric),
|
||||||
|
Map.entry(UpDownType.class, VerificationHelper::handleToString),
|
||||||
|
Map.entry(QuantityType.class, VerificationHelper::handleQuantityType),
|
||||||
|
Map.entry(RawType.class, VerificationHelper::handleRawType));
|
||||||
|
|
||||||
|
private static Pair<Object, Object> handleGeneric(Object ev, Document doc) {
|
||||||
|
Object value = doc.get(MongoDBFields.FIELD_VALUE);
|
||||||
|
return Pair.of(ev, value != null ? value : new Object());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Pair<Object, Object> handleToString(Object ev, Document doc) {
|
||||||
|
Object value = doc.get(MongoDBFields.FIELD_VALUE);
|
||||||
|
return Pair.of(ev.toString(), value != null ? value : new Object());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Pair<Object, Object> handleDecimalType(Object ev, Document doc) {
|
||||||
|
Double value = doc.getDouble(MongoDBFields.FIELD_VALUE);
|
||||||
|
return Pair.of(((DecimalType) ev).doubleValue(), value != null ? value : new Object());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Pair<Object, Object> handleDateTimeType(Object ev, Document doc) {
|
||||||
|
String value = doc.getString(MongoDBFields.FIELD_VALUE);
|
||||||
|
return Pair.of(((DateTimeType) ev).getZonedDateTime().toString(), value != null ? value : new Object());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Pair<Object, Object> handlePercentType(Object ev, Document doc) {
|
||||||
|
Integer value = doc.getInteger(MongoDBFields.FIELD_VALUE);
|
||||||
|
return Pair.of(((PercentType) ev).intValue(), value != null ? value : new Object());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Pair<Object, Object> handleQuantityType(Object ev, Document doc) {
|
||||||
|
Double value = doc.getDouble(MongoDBFields.FIELD_VALUE);
|
||||||
|
String unit = doc.getString(MongoDBFields.FIELD_UNIT);
|
||||||
|
if (value != null && unit != null) {
|
||||||
|
QuantityType<?> quantityType = (QuantityType<?>) ev;
|
||||||
|
return Pair.of(quantityType.doubleValue() + "--" + quantityType.getUnit(), value + "--" + unit);
|
||||||
|
}
|
||||||
|
return Pair.of(new Object(), new Object());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Pair<Object, Object> handleRawType(Object ev, Document doc) {
|
||||||
|
RawType rawType = (RawType) ev;
|
||||||
|
Document expectedDoc = new Document();
|
||||||
|
expectedDoc.put(MongoDBFields.FIELD_VALUE_TYPE, rawType.getMimeType());
|
||||||
|
expectedDoc.put(MongoDBFields.FIELD_VALUE_DATA, new Binary(rawType.getBytes()));
|
||||||
|
Object value = doc.get(MongoDBFields.FIELD_VALUE);
|
||||||
|
return Pair.of(expectedDoc, value != null ? value : new Object());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user