diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/frostserver/mqtt/subscription/AbstractSubscription.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/frostserver/mqtt/subscription/AbstractSubscription.java index cdac4644f..8092743e2 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/frostserver/mqtt/subscription/AbstractSubscription.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/frostserver/mqtt/subscription/AbstractSubscription.java @@ -40,6 +40,7 @@ import de.fraunhofer.iosb.ilt.frostserver.query.expression.constant.IntegerConstant; import de.fraunhofer.iosb.ilt.frostserver.query.expression.constant.StringConstant; import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.comparison.Equal; +import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.logical.And; import de.fraunhofer.iosb.ilt.frostserver.settings.CoreSettings; import java.io.IOException; import java.util.ArrayList; @@ -96,9 +97,9 @@ public boolean matches(PersistenceManager persistenceManager, Entity newEntity, return true; } - protected void generateFilter(int pathElementOffset) { + protected void generateFilter(int pathElementOffset, Expression extraFilter) { final List properties = new ArrayList<>(); - boolean direct = true; + boolean direct = extraFilter == null; final int size = path.size(); final int startIdx = size - 1 - pathElementOffset; if (startIdx < 0) { @@ -134,7 +135,7 @@ protected void generateFilter(int pathElementOffset) { properties.add(navProp); if (id != null) { - createMatchExpression(properties, epe); + createMatchExpression(properties, epe, extraFilter); // there should be at most two PathElements left, the EntitySetPath and the EntityPath now visiting assert (i <= 1); return; @@ -160,7 +161,7 @@ private void createMatcher(final NavigationPropertyMain navProp, PkValue pkValue }; } - private void createMatchExpression(List properties, final PathElementEntity epe) { + private void createMatchExpression(List properties, final PathElementEntity epe, Expression extraFilter) { final PrimaryKey primaryKey = entityType.getPrimaryKey(); properties.addAll(primaryKey.getKeyProperties()); String epeId = UrlHelper.quoteForUrl(primaryKey, epe.getPkValues()); @@ -169,6 +170,9 @@ private void createMatchExpression(List properties, final PathElementE } else { matchExpression = new Equal(new Path(properties), new IntegerConstant(epeId)); } + if (extraFilter != null) { + matchExpression = new And(matchExpression, extraFilter); + } query = new Query(modelRegistry, queryDefaults, path, ANONYMOUS_PRINCIPAL); query.setFilter(matchExpression); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/frostserver/mqtt/subscription/EntitySetSubscription.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/frostserver/mqtt/subscription/EntitySetSubscription.java index 57aaa1b65..01ab1d9f3 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/frostserver/mqtt/subscription/EntitySetSubscription.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/frostserver/mqtt/subscription/EntitySetSubscription.java @@ -27,6 +27,7 @@ import de.fraunhofer.iosb.ilt.frostserver.property.EntityPropertyMain; import de.fraunhofer.iosb.ilt.frostserver.query.Expand; import de.fraunhofer.iosb.ilt.frostserver.query.Query; +import de.fraunhofer.iosb.ilt.frostserver.query.expression.Expression; import de.fraunhofer.iosb.ilt.frostserver.settings.CoreSettings; import de.fraunhofer.iosb.ilt.frostserver.util.StringHelper; import de.fraunhofer.iosb.ilt.frostserver.util.exception.IncorrectRequestException; @@ -57,23 +58,30 @@ private void init() { String queryString = SubscriptionFactory.getQueryFromTopic(topic); query = parseQuery(queryString); - if (query != null && (query.getCount().isPresent() - || query.getFilter() != null - || !query.getOrderBy().isEmpty() - || query.getSkip().isPresent() - || query.getTop().isPresent())) { - throw new IllegalArgumentException("Invalid subscription to: '" + topic + "': only $select and $expand is allowed in query options."); - } else if (query != null && !query.getExpand().isEmpty()) { - Query queryCopy = parseQuery(queryString); - if (queryCopy != null) { - List expandList = queryCopy.getExpand(); - expandQuery = new Query(modelRegistry, queryDefaults, queryCopy.getPath()) - .setExpand(expandList) - .addSelect(entityType.getPrimaryKey().getKeyProperties()); + Expression filter = null; + if (query != null) { + if (query.getCount().isPresent() + || query.getFilter() != null + || !query.getOrderBy().isEmpty() + || query.getSkip().isPresent() + || query.getTop().isPresent()) { + throw new IllegalArgumentException("Invalid subscription to: '" + topic + "': only $select and $expand is allowed in query options."); + } + if (query.getFilter() != null && !settings.getMqttSettings().isAllowMqttFilter()) { + throw new IllegalArgumentException("Invalid subscription to: '" + topic + "': only $filter is not allowed in query options."); + } + filter = query.getFilter(); + if (!query.getExpand().isEmpty()) { + Query queryCopy = parseQuery(queryString); + if (queryCopy != null) { + List expandList = queryCopy.getExpand(); + expandQuery = new Query(modelRegistry, queryDefaults, queryCopy.getPath()) + .setExpand(expandList) + .addSelect(entityType.getPrimaryKey().getKeyProperties()); + } } } - - generateFilter(1); + generateFilter(1, filter); } private Query parseQuery(String topic) { diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/frostserver/mqtt/subscription/EntitySubscription.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/frostserver/mqtt/subscription/EntitySubscription.java index 29e4dacf3..5536be711 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/frostserver/mqtt/subscription/EntitySubscription.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/frostserver/mqtt/subscription/EntitySubscription.java @@ -60,7 +60,7 @@ private void init() { PkValue id = ((PathElementEntity) path.getLastElement()).getPkValues(); matcher = x -> x.getPrimaryKeyValues().equals(id); } - generateFilter(1); + generateFilter(1, null); } @Override diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/frostserver/mqtt/subscription/PropertySubscription.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/frostserver/mqtt/subscription/PropertySubscription.java index 91a533e81..78ab0d1bc 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/frostserver/mqtt/subscription/PropertySubscription.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/frostserver/mqtt/subscription/PropertySubscription.java @@ -62,7 +62,7 @@ private void init() { } query = new Query(modelRegistry, queryDefaults, path, ANONYMOUS_PRINCIPAL); query.addSelect(property); - generateFilter(2); + generateFilter(2, null); } @Override diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/frostserver/settings/MqttSettings.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/frostserver/settings/MqttSettings.java index 5e02bea94..597104eaf 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/frostserver/settings/MqttSettings.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/frostserver/settings/MqttSettings.java @@ -66,6 +66,8 @@ public class MqttSettings implements ConfigDefaults { public static final String TAG_CREATE_THREAD_POOL_SIZE = "CreateThreadPoolSize"; @DefaultValue("") public static final String TAG_EXPOSED_MQTT_ENDPOINTS = "exposedEndpoints"; + @DefaultValueBoolean(false) + public static final String TAG_MQTT_ALLOW_FILTER = "allowFilter"; /** * Constraints @@ -140,6 +142,12 @@ public class MqttSettings implements ConfigDefaults { * Number of threads used to process EntityCreateEvents. */ private int createThreadPoolSize; + + /** + * Flag indicating if $filter is allowed on MQTT topics or not. + */ + private boolean allowMqttFilter; + /** * Extension point for implementation specific settings. */ @@ -158,6 +166,7 @@ private void init(CoreSettings coreSettings, Settings customSettings) { mqttServerImplementationClass = customSettings.get(TAG_IMPLEMENTATION_CLASS, getClass()); enableMqtt = customSettings.getBoolean(TAG_ENABLED, getClass()); port = customSettings.getInt(TAG_PORT, getClass()); + allowMqttFilter = customSettings.getBoolean(TAG_MQTT_ALLOW_FILTER, getClass()); setHost(customSettings.get(TAG_HOST, getClass())); setInternalHost(customSettings.get(TAG_HOST_INTERNAL, getClass())); setSubscribeMessageQueueSize(customSettings.getInt(TAG_SUBSCRIBE_MESSAGE_QUEUE_SIZE, getClass())); @@ -342,4 +351,8 @@ public void setCreateThreadPoolSize(int createThreadPoolSize) { this.createThreadPoolSize = createThreadPoolSize; } + public boolean isAllowMqttFilter() { + return allowMqttFilter; + } + } diff --git a/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/AbstractTestClass.java b/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/AbstractTestClass.java index 230397e59..d57dd038a 100644 --- a/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/AbstractTestClass.java +++ b/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/AbstractTestClass.java @@ -28,6 +28,7 @@ import de.fraunhofer.iosb.ilt.frostclient.models.SensorThingsTaskingV11; import de.fraunhofer.iosb.ilt.frostserver.plugin.actuation.ActuationModelSettings; import de.fraunhofer.iosb.ilt.frostserver.plugin.multidatastream.MdsModelSettings; +import de.fraunhofer.iosb.ilt.frostserver.settings.MqttSettings; import java.io.IOException; import java.net.MalformedURLException; import java.net.URISyntaxException; @@ -73,6 +74,7 @@ public abstract class AbstractTestClass { defaultProperties.put(PREFIX_PLUGINS + ActuationModelSettings.TAG_ENABLE_ACTUATION, "true"); defaultProperties.put(PREFIX_PLUGINS + MdsModelSettings.TAG_ENABLE_MDS_MODEL, "true"); defaultProperties.put(TAG_FILTER_DELETE_ENABLE, "true"); + defaultProperties.put(PREFIX_MQTT + MqttSettings.TAG_MQTT_ALLOW_FILTER, "true"); defaultProperties.put(PREFIX_MQTT + "session.timeout.seconds", "100"); } diff --git a/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/c03filtering/FilterTests.java b/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/c03filtering/FilterTests.java index ad667fd69..c21895ff4 100644 --- a/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/c03filtering/FilterTests.java +++ b/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/c03filtering/FilterTests.java @@ -17,12 +17,13 @@ */ package de.fraunhofer.iosb.ilt.statests.c03filtering; -import static de.fraunhofer.iosb.ilt.frostclient.models.SensorThingsSensingV11.EP_PARAMETERS; import static de.fraunhofer.iosb.ilt.frostclient.models.SensorThingsSensingV11.EP_PROPERTIES; -import static de.fraunhofer.iosb.ilt.frostclient.models.SensorThingsSensingV11.EP_RESULTQUALITY; -import static de.fraunhofer.iosb.ilt.frostclient.models.SensorThingsSensingV11.EP_VALIDTIME; import static de.fraunhofer.iosb.ilt.frostclient.utils.CollectionsHelper.propertiesBuilder; import static de.fraunhofer.iosb.ilt.frostclient.utils.ParserUtils.formatKeyValuesForUrl; +import static de.fraunhofer.iosb.ilt.statests.util.EntityUtils.createDatastream; +import static de.fraunhofer.iosb.ilt.statests.util.EntityUtils.createObservationSet; +import static de.fraunhofer.iosb.ilt.statests.util.EntityUtils.createObservedProperty; +import static de.fraunhofer.iosb.ilt.statests.util.EntityUtils.createSensor; import static de.fraunhofer.iosb.ilt.statests.util.EntityUtils.testFilterResults; import static de.fraunhofer.iosb.ilt.statests.util.Utils.getFromList; import static org.junit.jupiter.api.Assertions.fail; @@ -30,10 +31,8 @@ import de.fraunhofer.iosb.ilt.frostclient.dao.Dao; import de.fraunhofer.iosb.ilt.frostclient.exception.ServiceFailureException; import de.fraunhofer.iosb.ilt.frostclient.model.Entity; -import de.fraunhofer.iosb.ilt.frostclient.models.ext.MapValue; import de.fraunhofer.iosb.ilt.frostclient.models.ext.TimeInterval; import de.fraunhofer.iosb.ilt.frostclient.models.ext.UnitOfMeasurement; -import de.fraunhofer.iosb.ilt.frostclient.utils.CollectionsHelper; import de.fraunhofer.iosb.ilt.statests.AbstractTestClass; import de.fraunhofer.iosb.ilt.statests.ServerVersion; import de.fraunhofer.iosb.ilt.statests.util.EntityUtils; @@ -41,12 +40,8 @@ import java.net.URISyntaxException; import java.time.Instant; import java.time.ZonedDateTime; -import java.time.temporal.ChronoUnit; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; import org.geojson.LineString; import org.geojson.LngLatAlt; import org.geojson.Point; @@ -192,104 +187,35 @@ private static void createEntities() throws ServiceFailureException, URISyntaxEx sSrvc.create(location); LOCATIONS.add(location); - createSensor("Sensor 0", "The sensor with idx 0.", "text", "Some metadata."); - createSensor("Sensor 1", "The sensor with idx 1.", "text", "Some metadata."); - createSensor("Sensor 2", "The sensor with idx 2.", "text", "Some metadata."); - createSensor("Sensor 3", "The sensor with idx 3.", "text", "Some metadata."); + createSensor(sSrvc, "Sensor 0", "The sensor with idx 0.", "text", "Some metadata.", SENSORS); + createSensor(sSrvc, "Sensor 1", "The sensor with idx 1.", "text", "Some metadata.", SENSORS); + createSensor(sSrvc, "Sensor 2", "The sensor with idx 2.", "text", "Some metadata.", SENSORS); + createSensor(sSrvc, "Sensor 3", "The sensor with idx 3.", "text", "Some metadata.", SENSORS); - createObservedProperty("ObservedProperty 0", "http://ucom.org/temperature", "ObservedProperty with index 0."); - createObservedProperty("ObservedProperty 1", "http://ucom.org/humidity", "ObservedProperty with index 1."); - createObservedProperty("ObservedProperty 2", "http://ucom.org/pressure", "ObservedProperty with index 2."); - createObservedProperty("ObservedProperty 3", "http://ucom.org/turbidity", "ObservedProperty with index 3."); + createObservedProperty(sSrvc, "ObservedProperty 0", "http://ucom.org/temperature", "ObservedProperty with index 0.", O_PROPS); + createObservedProperty(sSrvc, "ObservedProperty 1", "http://ucom.org/humidity", "ObservedProperty with index 1.", O_PROPS); + createObservedProperty(sSrvc, "ObservedProperty 2", "http://ucom.org/pressure", "ObservedProperty with index 2.", O_PROPS); + createObservedProperty(sSrvc, "ObservedProperty 3", "http://ucom.org/turbidity", "ObservedProperty with index 3.", O_PROPS); UnitOfMeasurement uomTemp = new UnitOfMeasurement("degree celcius", "°C", "ucum:T"); - createDatastream("Datastream 0", "Datastream 1 of thing 0, sensor 0.", "someType", uomTemp, THINGS.get(0), SENSORS.get(0), O_PROPS.get(0)); - createDatastream("Datastream 1", "Datastream 2 of thing 0, sensor 1.", "someType", uomTemp, THINGS.get(0), SENSORS.get(1), O_PROPS.get(1)); - createDatastream("Datastream 2", "Datastream 3 of thing 0, sensor 2.", "someType", uomTemp, THINGS.get(0), SENSORS.get(2), O_PROPS.get(2)); - createDatastream("Datastream 3", "Datastream 1 of thing 1, sensor 0.", "someType", uomTemp, THINGS.get(1), SENSORS.get(0), O_PROPS.get(0)); - createDatastream("Datastream 4", "Datastream 2 of thing 1, sensor 1.", "someType", uomTemp, THINGS.get(1), SENSORS.get(1), O_PROPS.get(1)); - createDatastream("Datastream 5", "Datastream 3 of thing 1, sensor 3.", "someType", uomTemp, THINGS.get(1), SENSORS.get(3), O_PROPS.get(3)); - createDatastream("Datastream 6", "Datastream 1 of thing 2, sensor 3.", "someType", uomTemp, THINGS.get(2), SENSORS.get(1), O_PROPS.get(0)); + createDatastream(sSrvc, "Datastream 0", "Datastream 1 of thing 0, sensor 0.", "someType", uomTemp, THINGS.get(0), SENSORS.get(0), O_PROPS.get(0), DATASTREAMS); + createDatastream(sSrvc, "Datastream 1", "Datastream 2 of thing 0, sensor 1.", "someType", uomTemp, THINGS.get(0), SENSORS.get(1), O_PROPS.get(1), DATASTREAMS); + createDatastream(sSrvc, "Datastream 2", "Datastream 3 of thing 0, sensor 2.", "someType", uomTemp, THINGS.get(0), SENSORS.get(2), O_PROPS.get(2), DATASTREAMS); + createDatastream(sSrvc, "Datastream 3", "Datastream 1 of thing 1, sensor 0.", "someType", uomTemp, THINGS.get(1), SENSORS.get(0), O_PROPS.get(0), DATASTREAMS); + createDatastream(sSrvc, "Datastream 4", "Datastream 2 of thing 1, sensor 1.", "someType", uomTemp, THINGS.get(1), SENSORS.get(1), O_PROPS.get(1), DATASTREAMS); + createDatastream(sSrvc, "Datastream 5", "Datastream 3 of thing 1, sensor 3.", "someType", uomTemp, THINGS.get(1), SENSORS.get(3), O_PROPS.get(3), DATASTREAMS); + createDatastream(sSrvc, "Datastream 6", "Datastream 1 of thing 2, sensor 3.", "someType", uomTemp, THINGS.get(2), SENSORS.get(1), O_PROPS.get(0), DATASTREAMS); ZonedDateTime startTime = ZonedDateTime.parse("2016-01-01T01:00:00.000Z"); TimeInterval startInterval = TimeInterval.create(Instant.parse("2016-01-01T01:00:00.000Z"), Instant.parse("2016-01-01T02:00:00.000Z")); - createObservationSet(DATASTREAMS.get(0), 0, startTime, startInterval, 6); - createObservationSet(DATASTREAMS.get(1), 3, startTime, startInterval, 6); - createObservationSet(DATASTREAMS.get(2), 6, startTime, startInterval, 6); - createObservationSet(DATASTREAMS.get(3), 9, startTime, startInterval, 6); - createObservationSet(DATASTREAMS.get(4), 12, startTime, startInterval, 6); - createObservationSet(DATASTREAMS.get(5), 15, startTime, startInterval, 6); - - } - - private static Entity createSensor(String name, String desc, String type, String metadata) throws ServiceFailureException { - int idx = SENSORS.size(); - MapValue properties = CollectionsHelper.propertiesBuilder() - .addItem("idx", idx) - .build(); - - Entity sensor = sMdl.newSensor(name, desc, type, metadata) - .setProperty(EP_PROPERTIES, properties); - sSrvc.create(sensor); - SENSORS.add(sensor); - return sensor; - } - - private static Entity createDatastream(String name, String desc, String type, UnitOfMeasurement uom, Entity thing, Entity sensor, Entity op) throws ServiceFailureException { - int idx = DATASTREAMS.size(); - MapValue properties = CollectionsHelper.propertiesBuilder() - .addItem("idx", idx) - .build(); - - Entity ds = sMdl.newDatastream(name, desc, type, uom) - .setProperty(EP_PROPERTIES, properties) - .setProperty(sMdl.npDatastreamThing, thing) - .setProperty(sMdl.npDatastreamSensor, sensor) - .setProperty(sMdl.npDatastreamObservedproperty, op); - sSrvc.create(ds); - DATASTREAMS.add(ds); - return ds; - } - - private static Entity createObservedProperty(String name, String definition, String description) throws ServiceFailureException { - int idx = O_PROPS.size(); - MapValue properties = CollectionsHelper.propertiesBuilder() - .addItem("idx", idx) - .build(); - Entity obsProp = sMdl.newObservedProperty(name, definition, description) - .setProperty(EP_PROPERTIES, properties); - sSrvc.create(obsProp); - O_PROPS.add(obsProp); - return obsProp; - } - - private static void createObservationSet(Entity datastream, long resultStart, ZonedDateTime phenomenonTimeStart, TimeInterval validTimeStart, long count) throws ServiceFailureException { - for (int i = 0; i < count; i++) { - ZonedDateTime phenTime = phenomenonTimeStart.plus(i, ChronoUnit.HOURS); - TimeInterval validTime = TimeInterval.create( - validTimeStart.getStart().plus(count, TimeUnit.HOURS), - validTimeStart.getEnd().plus(count, TimeUnit.HOURS)); - createObservation(datastream, resultStart + i, phenTime, validTime); - } - } - - private static Entity createObservation(Entity datastream, long result, ZonedDateTime phenomenonTime, TimeInterval validTime) throws ServiceFailureException { - int idx = OBSERVATIONS.size(); - Map parameters = new HashMap<>(); - parameters.put("idx", idx); - Entity obs = sMdl.newObservation(result, phenomenonTime, datastream) - .setProperty(EP_VALIDTIME, validTime) - .setProperty(EP_PARAMETERS, parameters); - if (idx % 2 == 0) { - obs.setProperty(EP_RESULTQUALITY, idx); - } else { - obs.setProperty(EP_RESULTQUALITY, "number-" + idx); - } - sSrvc.create(obs); - OBSERVATIONS.add(obs); - return obs; + createObservationSet(sSrvc, DATASTREAMS.get(0), 0, startTime, startInterval, 6, OBSERVATIONS); + createObservationSet(sSrvc, DATASTREAMS.get(1), 3, startTime, startInterval, 6, OBSERVATIONS); + createObservationSet(sSrvc, DATASTREAMS.get(2), 6, startTime, startInterval, 6, OBSERVATIONS); + createObservationSet(sSrvc, DATASTREAMS.get(3), 9, startTime, startInterval, 6, OBSERVATIONS); + createObservationSet(sSrvc, DATASTREAMS.get(4), 12, startTime, startInterval, 6, OBSERVATIONS); + createObservationSet(sSrvc, DATASTREAMS.get(5), 15, startTime, startInterval, 6, OBSERVATIONS); } /** diff --git a/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/c08mqttsubscribe/Capability8Tests.java b/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/c08mqttsubscribe/Capability8Tests.java index 3bd03372e..cc352f2c1 100644 --- a/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/c08mqttsubscribe/Capability8Tests.java +++ b/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/c08mqttsubscribe/Capability8Tests.java @@ -544,17 +544,6 @@ private void checkSubscribePatch(EntityType entityType, List selectedPro mqttHelper.getTopic(entityType, selectedProperties)); } - private JsonNode filterEntity(JsonNode entity, List selectedProperties) { - Iterator iterator = entity.fieldNames(); - while (iterator.hasNext()) { - String key = iterator.next().toString(); - if (!selectedProperties.contains(key)) { - iterator.remove(); - } - } - return entity; - } - private Callable getDeepInsertEntityAction(EntityType entityType) { Callable trigger = () -> { switch (entityType) { diff --git a/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/c08mqttsubscribe/MqttExtraTests.java b/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/c08mqttsubscribe/MqttExtraTests.java new file mode 100644 index 000000000..42eb6464e --- /dev/null +++ b/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/c08mqttsubscribe/MqttExtraTests.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2023 Fraunhofer Institut IOSB, Fraunhoferstr. 1, D 76131 + * Karlsruhe, Germany. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package de.fraunhofer.iosb.ilt.statests.c08mqttsubscribe; + +import static de.fraunhofer.iosb.ilt.frostclient.models.SensorThingsSensingV11.EP_PROPERTIES; +import static de.fraunhofer.iosb.ilt.frostclient.utils.CollectionsHelper.propertiesBuilder; +import static de.fraunhofer.iosb.ilt.statests.util.EntityUtils.createDatastream; +import static de.fraunhofer.iosb.ilt.statests.util.EntityUtils.createObservedProperty; +import static de.fraunhofer.iosb.ilt.statests.util.EntityUtils.createSensor; + +import de.fraunhofer.iosb.ilt.frostclient.exception.ServiceFailureException; +import de.fraunhofer.iosb.ilt.frostclient.model.Entity; +import de.fraunhofer.iosb.ilt.frostclient.models.ext.UnitOfMeasurement; +import de.fraunhofer.iosb.ilt.statests.AbstractTestClass; +import de.fraunhofer.iosb.ilt.statests.ServerVersion; +import de.fraunhofer.iosb.ilt.statests.util.EntityHelper; +import de.fraunhofer.iosb.ilt.statests.util.mqtt.MqttHelper; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.List; +import org.geojson.LineString; +import org.geojson.LngLatAlt; +import org.geojson.Point; +import org.geojson.Polygon; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.TestMethodOrder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Tests non-standard MQTT extensions + */ +@TestMethodOrder(MethodOrderer.MethodName.class) +public abstract class MqttExtraTests extends AbstractTestClass { + + private static final Logger LOGGER = LoggerFactory.getLogger(MqttExtraTests.class.getName()); + + private static final List THINGS = new ArrayList<>(); + private static final List LOCATIONS = new ArrayList<>(); + private static final List SENSORS = new ArrayList<>(); + private static final List O_PROPS = new ArrayList<>(); + private static final List DATASTREAMS = new ArrayList<>(); + private static final List OBSERVATIONS = new ArrayList<>(); + + private static EntityHelper entityHelper; + private static MqttHelper mqttHelper; + + public MqttExtraTests(ServerVersion serverVersion) { + super(serverVersion); + } + + @Override + protected void setUpVersion() throws ServiceFailureException, URISyntaxException { + LOGGER.info("Setting up for version {}.", version.urlPart); + entityHelper = new EntityHelper(version, serverSettings); + mqttHelper = new MqttHelper(version, serverSettings.getMqttUrl(), serverSettings.getMqttTimeOut()); + createEntities(); + } + + @Override + protected void tearDownVersion() { + entityHelper.deleteEverything(); + entityHelper = null; + mqttHelper = null; + } + + @AfterAll + public static void tearDown() { + LOGGER.info("Tearing down."); + entityHelper.deleteEverything(); + entityHelper = null; + mqttHelper = null; + } + + private static void createEntities() throws ServiceFailureException, URISyntaxException { + Entity thing = sMdl.newThing("Thing 1", "The first thing."); + sSrvc.create(thing); + THINGS.add(thing); + + thing = sMdl.newThing("Thing 2", "The second thing.") + .setProperty(EP_PROPERTIES, propertiesBuilder().addItem("field", 2).build()); + sSrvc.create(thing); + THINGS.add(thing); + + thing = sMdl.newThing("Thing 3", "The third thing.") + .setProperty(EP_PROPERTIES, propertiesBuilder().addItem("field", 3).build()); + sSrvc.create(thing); + THINGS.add(thing); + + thing = sMdl.newThing("Thing 4", "The fourth thing."); + sSrvc.create(thing); + THINGS.add(thing); + + // Locations 0 + Entity location = sMdl.newLocation("Location 1.0", "First Location of Thing 1.", "application/vnd.geo+json", new Point(8, 51)) + .setProperty(EP_PROPERTIES, propertiesBuilder().addItem("field", 1).build()) + .addNavigationEntity(sMdl.npLocationThings, THINGS.get(0)); + sSrvc.create(location); + LOCATIONS.add(location); + + // Locations 1 + location = sMdl.newLocation("Location 1.1", "Second Location of Thing 1.", "application/vnd.geo+json", new Point(8, 52)) + .setProperty(EP_PROPERTIES, propertiesBuilder().addItem("field", 1.1).build()) + .addNavigationEntity(sMdl.npLocationThings, THINGS.get(0)); + sSrvc.create(location); + LOCATIONS.add(location); + + // Locations 2 + location = sMdl.newLocation("Location 2", "Location of Thing 2.", "application/vnd.geo+json", new Point(8, 53)) + .setProperty(EP_PROPERTIES, propertiesBuilder().addItem("field", 2).build()) + .addNavigationEntity(sMdl.npLocationThings, THINGS.get(1)); + sSrvc.create(location); + LOCATIONS.add(location); + + // Locations 3 + location = sMdl.newLocation("Location 3", "Location of Thing 3.", "application/vnd.geo+json", new Point(8, 54)) + .setProperty(EP_PROPERTIES, propertiesBuilder().addItem("field", 3).build()) + .addNavigationEntity(sMdl.npLocationThings, THINGS.get(2)); + sSrvc.create(location); + LOCATIONS.add(location); + + // Locations 4 + location = sMdl.newLocation("Location 4", "Location of Thing 4.", "application/vnd.geo+json", + new Polygon( + new LngLatAlt(8, 53), + new LngLatAlt(7, 52), + new LngLatAlt(7, 53), + new LngLatAlt(8, 53))) + .setProperty(EP_PROPERTIES, propertiesBuilder().addItem("field", 4).build()) + .addNavigationEntity(sMdl.npLocationThings, THINGS.get(3)); + sSrvc.create(location); + LOCATIONS.add(location); + + // Locations 5 + location = sMdl.newLocation("Location 5", "A line.", "application/vnd.geo+json", + new LineString( + new LngLatAlt(5, 52), + new LngLatAlt(5, 53))) + .setProperty(EP_PROPERTIES, propertiesBuilder().addItem("field", 5).build()); + sSrvc.create(location); + LOCATIONS.add(location); + + // Locations 6 + location = sMdl.newLocation("Location 6", "A longer line.", "application/vnd.geo+json", + new LineString( + new LngLatAlt(5, 52), + new LngLatAlt(6, 53))) + .setProperty(EP_PROPERTIES, propertiesBuilder().addItem("field", 6).build()); + sSrvc.create(location); + LOCATIONS.add(location); + + // Locations 7 + location = sMdl.newLocation("Location 7", "The longest line.", "application/vnd.geo+json", + new LineString( + new LngLatAlt(4, 52), + new LngLatAlt(8, 52))) + .setProperty(EP_PROPERTIES, propertiesBuilder().addItem("field", 7).build()); + sSrvc.create(location); + LOCATIONS.add(location); + + createSensor(sSrvc, "Sensor 0", "The sensor with idx 0.", "text", "Some metadata.", SENSORS); + createSensor(sSrvc, "Sensor 1", "The sensor with idx 1.", "text", "Some metadata.", SENSORS); + createSensor(sSrvc, "Sensor 2", "The sensor with idx 2.", "text", "Some metadata.", SENSORS); + createSensor(sSrvc, "Sensor 3", "The sensor with idx 3.", "text", "Some metadata.", SENSORS); + + createObservedProperty(sSrvc, "ObservedProperty 0", "http://ucom.org/temperature", "ObservedProperty with index 0.", O_PROPS); + createObservedProperty(sSrvc, "ObservedProperty 1", "http://ucom.org/humidity", "ObservedProperty with index 1.", O_PROPS); + createObservedProperty(sSrvc, "ObservedProperty 2", "http://ucom.org/pressure", "ObservedProperty with index 2.", O_PROPS); + createObservedProperty(sSrvc, "ObservedProperty 3", "http://ucom.org/turbidity", "ObservedProperty with index 3.", O_PROPS); + + UnitOfMeasurement uomTemp = new UnitOfMeasurement("degree celcius", "°C", "ucum:T"); + + createDatastream(sSrvc, "Datastream 0", "Datastream 1 of thing 0, sensor 0.", "someType", uomTemp, THINGS.get(0), SENSORS.get(0), O_PROPS.get(0), DATASTREAMS); + createDatastream(sSrvc, "Datastream 1", "Datastream 2 of thing 0, sensor 1.", "someType", uomTemp, THINGS.get(0), SENSORS.get(1), O_PROPS.get(1), DATASTREAMS); + createDatastream(sSrvc, "Datastream 2", "Datastream 3 of thing 0, sensor 2.", "someType", uomTemp, THINGS.get(0), SENSORS.get(2), O_PROPS.get(2), DATASTREAMS); + createDatastream(sSrvc, "Datastream 3", "Datastream 1 of thing 1, sensor 0.", "someType", uomTemp, THINGS.get(1), SENSORS.get(0), O_PROPS.get(0), DATASTREAMS); + createDatastream(sSrvc, "Datastream 4", "Datastream 2 of thing 1, sensor 1.", "someType", uomTemp, THINGS.get(1), SENSORS.get(1), O_PROPS.get(1), DATASTREAMS); + createDatastream(sSrvc, "Datastream 5", "Datastream 3 of thing 1, sensor 3.", "someType", uomTemp, THINGS.get(1), SENSORS.get(3), O_PROPS.get(3), DATASTREAMS); + createDatastream(sSrvc, "Datastream 6", "Datastream 1 of thing 2, sensor 3.", "someType", uomTemp, THINGS.get(2), SENSORS.get(1), O_PROPS.get(0), DATASTREAMS); + } + +} diff --git a/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/util/EntityUtils.java b/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/util/EntityUtils.java index ac1491d26..0ca20f8c6 100644 --- a/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/util/EntityUtils.java +++ b/FROST-Server.Tests/src/test/java/de/fraunhofer/iosb/ilt/statests/util/EntityUtils.java @@ -17,6 +17,10 @@ */ package de.fraunhofer.iosb.ilt.statests.util; +import static de.fraunhofer.iosb.ilt.frostclient.models.SensorThingsSensingV11.EP_PARAMETERS; +import static de.fraunhofer.iosb.ilt.frostclient.models.SensorThingsSensingV11.EP_PROPERTIES; +import static de.fraunhofer.iosb.ilt.frostclient.models.SensorThingsSensingV11.EP_RESULTQUALITY; +import static de.fraunhofer.iosb.ilt.frostclient.models.SensorThingsSensingV11.EP_VALIDTIME; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -33,12 +37,22 @@ import de.fraunhofer.iosb.ilt.frostclient.model.ModelRegistry; import de.fraunhofer.iosb.ilt.frostclient.model.property.EntityPropertyMain; import de.fraunhofer.iosb.ilt.frostclient.model.property.NavigationPropertyEntity; +import de.fraunhofer.iosb.ilt.frostclient.models.SensorThingsSensingV11; +import de.fraunhofer.iosb.ilt.frostclient.models.ext.MapValue; +import de.fraunhofer.iosb.ilt.frostclient.models.ext.TimeInterval; +import de.fraunhofer.iosb.ilt.frostclient.models.ext.UnitOfMeasurement; +import de.fraunhofer.iosb.ilt.frostclient.utils.CollectionsHelper; import de.fraunhofer.iosb.ilt.statests.StaService; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Assertions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -503,4 +517,75 @@ public static void filterForException(Dao doa, String filter, int expectedCode) } fail("Filter " + filter + " did not respond with " + expectedCode + "."); } + + public static Entity createSensor(SensorThingsService srvc, String name, String desc, String type, String metadata, List registry) throws ServiceFailureException { + int idx = registry.size(); + MapValue properties = CollectionsHelper.propertiesBuilder() + .addItem("idx", idx) + .build(); + SensorThingsSensingV11 sMdl = srvc.getModel(SensorThingsSensingV11.class); + Entity sensor = sMdl.newSensor(name, desc, type, metadata) + .setProperty(EP_PROPERTIES, properties); + srvc.create(sensor); + registry.add(sensor); + return sensor; + } + + public static Entity createDatastream(SensorThingsService srvc, String name, String desc, String type, UnitOfMeasurement uom, Entity thing, Entity sensor, Entity op, List registry) throws ServiceFailureException { + int idx = registry.size(); + MapValue properties = CollectionsHelper.propertiesBuilder() + .addItem("idx", idx) + .build(); + SensorThingsSensingV11 sMdl = srvc.getModel(SensorThingsSensingV11.class); + Entity ds = sMdl.newDatastream(name, desc, type, uom) + .setProperty(EP_PROPERTIES, properties) + .setProperty(sMdl.npDatastreamThing, thing) + .setProperty(sMdl.npDatastreamSensor, sensor) + .setProperty(sMdl.npDatastreamObservedproperty, op); + srvc.create(ds); + registry.add(ds); + return ds; + } + + public static Entity createObservedProperty(SensorThingsService srvc, String name, String definition, String description, List registry) throws ServiceFailureException { + int idx = registry.size(); + MapValue properties = CollectionsHelper.propertiesBuilder() + .addItem("idx", idx) + .build(); + SensorThingsSensingV11 sMdl = srvc.getModel(SensorThingsSensingV11.class); + Entity obsProp = sMdl.newObservedProperty(name, definition, description) + .setProperty(EP_PROPERTIES, properties); + srvc.create(obsProp); + registry.add(obsProp); + return obsProp; + } + + public static void createObservationSet(SensorThingsService srvc, Entity datastream, long resultStart, ZonedDateTime phenomenonTimeStart, TimeInterval validTimeStart, long count, List registry) throws ServiceFailureException { + for (int i = 0; i < count; i++) { + ZonedDateTime phenTime = phenomenonTimeStart.plus(i, ChronoUnit.HOURS); + TimeInterval validTime = TimeInterval.create( + validTimeStart.getStart().plus(count, TimeUnit.HOURS), + validTimeStart.getEnd().plus(count, TimeUnit.HOURS)); + createObservation(srvc, datastream, resultStart + i, phenTime, validTime, registry); + } + } + + public static Entity createObservation(SensorThingsService srvc, Entity datastream, long result, ZonedDateTime phenomenonTime, TimeInterval validTime, List registry) throws ServiceFailureException { + int idx = registry.size(); + Map parameters = new HashMap<>(); + parameters.put("idx", idx); + SensorThingsSensingV11 sMdl = srvc.getModel(SensorThingsSensingV11.class); + Entity obs = sMdl.newObservation(result, phenomenonTime, datastream) + .setProperty(EP_VALIDTIME, validTime) + .setProperty(EP_PARAMETERS, parameters); + if (idx % 2 == 0) { + obs.setProperty(EP_RESULTQUALITY, idx); + } else { + obs.setProperty(EP_RESULTQUALITY, "number-" + idx); + } + srvc.create(obs); + registry.add(obs); + return obs; + } + }