mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-25 14:55:55 +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>
This commit is contained in:
parent
2db9fb027d
commit
956b8e47d5
@ -14,12 +14,66 @@
|
||||
|
||||
<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>
|
||||
<!-- https://mvnrepository.com/artifact/org.mongodb/mongo-java-driver -->
|
||||
<dependency>
|
||||
<groupId>org.mongodb</groupId>
|
||||
<artifactId>mongo-java-driver</artifactId>
|
||||
<version>2.13.1</version>
|
||||
<artifactId>mongodb-driver-sync</artifactId>
|
||||
<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>
|
||||
</dependencies>
|
||||
</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.time.ZonedDateTime;
|
||||
import java.util.Date;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.persistence.HistoricItem;
|
||||
@ -54,6 +55,7 @@ public class MongoDBItem implements HistoricItem {
|
||||
|
||||
@Override
|
||||
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.Set;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.bson.types.ObjectId;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.items.Item;
|
||||
import org.openhab.core.items.ItemNotFoundException;
|
||||
import org.openhab.core.items.ItemRegistry;
|
||||
import org.openhab.core.library.items.ContactItem;
|
||||
import org.openhab.core.library.items.DateTimeItem;
|
||||
import org.openhab.core.library.items.DimmerItem;
|
||||
import org.openhab.core.library.items.NumberItem;
|
||||
import org.openhab.core.library.items.RollershutterItem;
|
||||
import org.openhab.core.library.items.SwitchItem;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
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.HistoricItem;
|
||||
import org.openhab.core.persistence.ModifiablePersistenceService;
|
||||
import org.openhab.core.persistence.PersistenceItemInfo;
|
||||
import org.openhab.core.persistence.PersistenceService;
|
||||
import org.openhab.core.persistence.QueryablePersistenceService;
|
||||
@ -59,29 +50,23 @@ import org.osgi.service.component.annotations.Reference;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBCollection;
|
||||
import com.mongodb.DBCursor;
|
||||
import com.mongodb.DBObject;
|
||||
import com.mongodb.MongoClient;
|
||||
import com.mongodb.MongoClientURI;
|
||||
import com.mongodb.client.MongoClient;
|
||||
import com.mongodb.client.MongoClients;
|
||||
import com.mongodb.client.MongoCollection;
|
||||
import com.mongodb.client.MongoCursor;
|
||||
import com.mongodb.client.result.DeleteResult;
|
||||
|
||||
/**
|
||||
* This is the implementation of the MongoDB {@link PersistenceService}.
|
||||
*
|
||||
* @author Thorsten Hoeger - Initial contribution
|
||||
* @author Stephan Brunner - Query fixes, Cleanup
|
||||
* @author René Ulbricht - Fixes type handling, driver update and cleanup
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = { PersistenceService.class,
|
||||
QueryablePersistenceService.class }, configurationPid = "org.openhab.mongodb", configurationPolicy = ConfigurationPolicy.REQUIRE)
|
||||
public class MongoDBPersistenceService implements QueryablePersistenceService {
|
||||
|
||||
private static final String FIELD_ID = "_id";
|
||||
private static final String FIELD_ITEM = "item";
|
||||
private static final String FIELD_REALNAME = "realName";
|
||||
private static final String FIELD_TIMESTAMP = "timestamp";
|
||||
private static final String FIELD_VALUE = "value";
|
||||
@Component(service = { PersistenceService.class, QueryablePersistenceService.class,
|
||||
ModifiablePersistenceService.class }, configurationPid = "org.openhab.mongodb", configurationPolicy = ConfigurationPolicy.REQUIRE)
|
||||
public class MongoDBPersistenceService implements ModifiablePersistenceService {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(MongoDBPersistenceService.class);
|
||||
|
||||
@ -150,10 +135,206 @@ public class MongoDBPersistenceService implements QueryablePersistenceService {
|
||||
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
|
||||
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
|
||||
if (item.getState() instanceof UnDefType) {
|
||||
if (state instanceof UnDefType) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -176,7 +357,7 @@ public class MongoDBPersistenceService implements QueryablePersistenceService {
|
||||
String collectionName = collectionPerItem ? realItemName : this.collection;
|
||||
|
||||
@Nullable
|
||||
DBCollection collection = connectToCollection(collectionName);
|
||||
MongoCollection<Document> collection = connectToCollection(collectionName);
|
||||
|
||||
if (collection == null) {
|
||||
// Logging is done in connectToCollection()
|
||||
@ -184,270 +365,123 @@ public class MongoDBPersistenceService implements QueryablePersistenceService {
|
||||
}
|
||||
|
||||
String name = (alias != null) ? alias : realItemName;
|
||||
Object value = this.convertValue(item.getState());
|
||||
|
||||
DBObject obj = new BasicDBObject();
|
||||
obj.put(FIELD_ID, new ObjectId());
|
||||
obj.put(FIELD_ITEM, name);
|
||||
obj.put(FIELD_REALNAME, realItemName);
|
||||
obj.put(FIELD_TIMESTAMP, new Date());
|
||||
obj.put(FIELD_VALUE, value);
|
||||
collection.save(obj);
|
||||
Object value = MongoDBTypeConversions.convertValue(state);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private Object convertValue(State state) {
|
||||
Object value;
|
||||
if (state instanceof PercentType type) {
|
||||
value = type.toBigDecimal().doubleValue();
|
||||
} else if (state instanceof DateTimeType type) {
|
||||
value = Date.from(type.getZonedDateTime().toInstant());
|
||||
} else if (state instanceof DecimalType type) {
|
||||
value = type.toBigDecimal().doubleValue();
|
||||
} else {
|
||||
value = state.toString();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@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);
|
||||
@Nullable
|
||||
public MongoCollection<Document> prepareCollection(FilterCriteria filter) {
|
||||
if (!initialized || !tryConnectToDatabase()) {
|
||||
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();
|
||||
if (realItemName == null) {
|
||||
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;
|
||||
@Nullable
|
||||
DBCollection collection = connectToCollection(collectionName);
|
||||
MongoCollection<Document> collection = connectToCollection(collectionName);
|
||||
|
||||
// If collection creation failed, return nothing.
|
||||
if (collection == null) {
|
||||
// Logging is done in connectToCollection()
|
||||
return Collections.emptyList();
|
||||
logger.warn("Failed to connect to collection {}", collectionName);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Item item = getItem(realItemName);
|
||||
return collection;
|
||||
}
|
||||
|
||||
if (item == null) {
|
||||
logger.warn("Item {} not found", realItemName);
|
||||
return Collections.emptyList();
|
||||
@Nullable
|
||||
private Document createQuery(FilterCriteria filter) {
|
||||
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<>();
|
||||
BasicDBObject query = new BasicDBObject();
|
||||
if (filter.getItemName() != null) {
|
||||
query.put(FIELD_ITEM, filter.getItemName());
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
||||
private boolean addStateToQuery(FilterCriteria filter, Document query) {
|
||||
State filterState = filter.getState();
|
||||
if (filterState != null && filter.getOperator() != null) {
|
||||
@Nullable
|
||||
String op = convertOperator(filter.getOperator());
|
||||
if (filterState != null) {
|
||||
String op = MongoDBTypeConversions.convertOperator(filter.getOperator());
|
||||
|
||||
if (op == null) {
|
||||
logger.error("Failed to convert operator {} to MongoDB operator", filter.getOperator());
|
||||
return Collections.emptyList();
|
||||
return false;
|
||||
}
|
||||
|
||||
Object value = convertValue(filterState);
|
||||
query.put(FIELD_VALUE, new BasicDBObject(op, value));
|
||||
Object value = MongoDBTypeConversions.convertValue(filterState);
|
||||
query.put(MongoDBFields.FIELD_VALUE, new Document(op, value));
|
||||
}
|
||||
|
||||
BasicDBObject dateQueries = new BasicDBObject();
|
||||
if (filter.getBeginDate() != null) {
|
||||
dateQueries.put("$gte", Date.from(filter.getBeginDate().toInstant()));
|
||||
return true;
|
||||
}
|
||||
|
||||
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) {
|
||||
dateQueries.put("$lte", Date.from(filter.getEndDate().toInstant()));
|
||||
ZonedDateTime endDate = filter.getEndDate();
|
||||
if (endDate != null) {
|
||||
dateQueries.put("$lte", Date.from(endDate.toInstant()));
|
||||
}
|
||||
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);
|
||||
|
||||
Integer sortDir = (filter.getOrdering() == Ordering.ASCENDING) ? 1 : -1;
|
||||
DBCursor cursor = collection.find(query).sort(new BasicDBObject(FIELD_TIMESTAMP, sortDir))
|
||||
.skip(filter.getPageNumber() * filter.getPageSize()).limit(filter.getPageSize());
|
||||
DeleteResult result = collection.deleteMany(query);
|
||||
|
||||
while (cursor.hasNext()) {
|
||||
BasicDBObject obj = (BasicDBObject) cursor.next();
|
||||
|
||||
final State state;
|
||||
if (item instanceof NumberItem) {
|
||||
state = new DecimalType(obj.getDouble(FIELD_VALUE));
|
||||
} else if (item instanceof DimmerItem) {
|
||||
state = new PercentType(obj.getInt(FIELD_VALUE));
|
||||
} else if (item instanceof SwitchItem) {
|
||||
state = OnOffType.valueOf(obj.getString(FIELD_VALUE));
|
||||
} else if (item instanceof ContactItem) {
|
||||
state = OpenClosedType.valueOf(obj.getString(FIELD_VALUE));
|
||||
} else if (item instanceof RollershutterItem) {
|
||||
state = new PercentType(obj.getInt(FIELD_VALUE));
|
||||
} else if (item instanceof DateTimeItem) {
|
||||
state = new DateTimeType(
|
||||
ZonedDateTime.ofInstant(obj.getDate(FIELD_VALUE).toInstant(), ZoneId.systemDefault()));
|
||||
} else {
|
||||
state = new StringType(obj.getString(FIELD_VALUE));
|
||||
}
|
||||
|
||||
items.add(new MongoDBItem(realItemName, state,
|
||||
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();
|
||||
logger.debug("Deleted {} documents", result.getDeletedCount());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -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