diff --git a/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityActor.java b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityActor.java index 350a5776cf..2959bfc8eb 100644 --- a/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityActor.java @@ -21,6 +21,7 @@ import org.thingsboard.server.actors.TbActorCtx; import org.thingsboard.server.actors.TbActorException; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.TbActorStopReason; import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg; import org.thingsboard.server.common.msg.cf.CalculatedFieldPartitionChangeMsg; @@ -47,6 +48,12 @@ public class CalculatedFieldEntityActor extends AbstractCalculatedFieldActor { } } + @Override + public void destroy(TbActorStopReason stopReason, Throwable cause) throws TbActorException { + log.debug("[{}] Stopping CF entity actor.", processor.tenantId); + processor.stop(); + } + @Override protected boolean doProcessCfMsg(ToCalculatedFieldSystemMsg msg) throws CalculatedFieldException { switch (msg.getMsgType()) { diff --git a/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java index a185b71d56..4e15dba120 100644 --- a/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java @@ -92,6 +92,12 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM this.ctx = ctx; } + public void stop() { + log.info("[{}][{}] Stopping entity actor.", tenantId, entityId); + states.clear(); + ctx.stop(ctx.getSelf()); + } + public void process(CalculatedFieldPartitionChangeMsg msg) { if (!systemContext.getPartitionService().resolve(ServiceType.TB_RULE_ENGINE, DataConstants.CF_QUEUE_NAME, tenantId, entityId).isMyPartition()) { log.info("[{}] Stopping entity actor due to change partition event.", entityId); diff --git a/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerActor.java b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerActor.java index a5c935e83f..70ed2849e8 100644 --- a/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerActor.java @@ -20,6 +20,7 @@ import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.TbActorCtx; import org.thingsboard.server.actors.TbActorException; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.TbActorStopReason; import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg; import org.thingsboard.server.common.msg.cf.CalculatedFieldEntityLifecycleMsg; import org.thingsboard.server.common.msg.cf.CalculatedFieldInitMsg; @@ -52,6 +53,12 @@ public class CalculatedFieldManagerActor extends AbstractCalculatedFieldActor { } } + @Override + public void destroy(TbActorStopReason stopReason, Throwable cause) throws TbActorException { + log.debug("[{}] Stopping CF manager actor.", processor.tenantId); + processor.stop(); + } + @Override protected boolean doProcessCfMsg(ToCalculatedFieldSystemMsg msg) throws CalculatedFieldException { switch (msg.getMsgType()) { diff --git a/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerMessageProcessor.java index fc48e9ad3e..0990365748 100644 --- a/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerMessageProcessor.java @@ -56,7 +56,6 @@ import java.util.concurrent.CopyOnWriteArrayList; import static org.thingsboard.server.utils.CalculatedFieldUtils.fromProto; - /** * @author Andrew Shvayka */ @@ -92,8 +91,17 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware this.ctx = ctx; } + public void stop() { + log.info("[{}] Stopping CF manager actor.", tenantId); + calculatedFields.values().forEach(CalculatedFieldCtx::stop); + calculatedFields.clear(); + entityIdCalculatedFields.clear(); + entityIdCalculatedFieldLinks.clear(); + ctx.stop(ctx.getSelf()); + } + public void onFieldInitMsg(CalculatedFieldInitMsg msg) throws CalculatedFieldException { - log.info("[{}] Processing CF init message.", msg.getCf().getId()); + log.debug("[{}] Processing CF init message.", msg.getCf().getId()); var cf = msg.getCf(); var cfCtx = new CalculatedFieldCtx(cf, systemContext.getTbelInvokeService(), systemContext.getApiLimitService()); try { @@ -109,7 +117,7 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware } public void onLinkInitMsg(CalculatedFieldLinkInitMsg msg) { - log.info("[{}] Processing CF link init message for entity [{}].", msg.getLink().getCalculatedFieldId(), msg.getLink().getEntityId()); + log.debug("[{}] Processing CF link init message for entity [{}].", msg.getLink().getCalculatedFieldId(), msg.getLink().getEntityId()); var link = msg.getLink(); // We use copy on write lists to safely pass the reference to another actor for the iteration. // Alternative approach would be to use any list but avoid modifications to the list (change the complete map value instead) @@ -122,7 +130,9 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware var calculatedField = calculatedFields.get(cfId); if (calculatedField != null) { - msg.getState().setRequiredArguments(calculatedField.getArgNames()); + if (msg.getState() != null) { + msg.getState().setRequiredArguments(calculatedField.getArgNames()); + } log.debug("Pushing CF state restore msg to specific actor [{}]", msg.getId().entityId()); getOrCreateActor(msg.getId().entityId()).tell(msg); } else { @@ -131,7 +141,7 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware } public void onEntityLifecycleMsg(CalculatedFieldEntityLifecycleMsg msg) throws CalculatedFieldException { - log.info("Processing entity lifecycle event: [{}] for entity: [{}]", msg.getData().getEvent(), msg.getData().getEntityId()); + log.debug("Processing entity lifecycle event: [{}] for entity: [{}]", msg.getData().getEvent(), msg.getData().getEntityId()); var entityType = msg.getData().getEntityId().getEntityType(); var event = msg.getData().getEvent(); switch (entityType) { diff --git a/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java b/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java index 846bde508d..728f715af4 100644 --- a/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java @@ -28,6 +28,7 @@ import org.thingsboard.server.actors.TbEntityActorId; import org.thingsboard.server.actors.TbEntityTypeActorIdPredicate; import org.thingsboard.server.actors.TbStringActorId; import org.thingsboard.server.actors.calculatedField.CalculatedFieldManagerActorCreator; +import org.thingsboard.server.actors.calculatedField.CalculatedFieldStateRestoreMsg; import org.thingsboard.server.actors.device.DeviceActorCreator; import org.thingsboard.server.actors.ruleChain.RuleChainManagerActor; import org.thingsboard.server.actors.service.ContextBasedCreator; @@ -126,6 +127,9 @@ public class TenantActor extends RuleChainManagerActor { @Override public void destroy(TbActorStopReason stopReason, Throwable cause) { log.info("[{}] Stopping tenant actor.", tenantId); + if (cfActor != null) { + ctx.stop(cfActor.getActorId()); + } } @Override @@ -190,7 +194,11 @@ public class TenantActor extends RuleChainManagerActor { private void onToCalculatedFieldSystemActorMsg(ToCalculatedFieldSystemMsg msg, boolean priority) { if (cfActor == null) { - log.warn("[{}] CF Actor is not initialized.", tenantId); + if (msg instanceof CalculatedFieldStateRestoreMsg) { + log.warn("[{}] CF Actor is not initialized. ToCalculatedFieldSystemMsg: [{}]", tenantId, msg); + } else { + log.debug("[{}] CF Actor is not initialized. ToCalculatedFieldSystemMsg: [{}]", tenantId, msg); + } return; } if (priority) { diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java index 0c4352dcea..fdbbb37568 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java @@ -122,6 +122,15 @@ public class CalculatedFieldCtx { } } + public void stop() { + if (calculatedFieldScriptEngine != null) { + calculatedFieldScriptEngine.destroy(); + } + if (customExpression != null) { + customExpression.remove(); + } + } + private CalculatedFieldScriptEngine initEngine(TenantId tenantId, String expression, TbelInvokeService tbelInvokeService) { if (tbelInvokeService == null) { throw new IllegalArgumentException("TBEL script engine is disabled!"); diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCalculatedFieldConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCalculatedFieldConsumerService.java index ac62576714..9ae06309c3 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCalculatedFieldConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCalculatedFieldConsumerService.java @@ -210,7 +210,7 @@ public class DefaultTbCalculatedFieldConsumerService extends AbstractConsumerSer @Override protected TbQueueConsumer> createNotificationsConsumer() { - return queueFactory.createToCalculatedFieldNotificationsMsgConsumer(); + return queueFactory.createToCalculatedFieldNotificationMsgConsumer(); } @Override diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryMonolithQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryMonolithQueueFactory.java index fac2cf5d5e..bd8b4bd4f8 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryMonolithQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryMonolithQueueFactory.java @@ -149,7 +149,7 @@ public class InMemoryMonolithQueueFactory implements TbCoreQueueFactory, TbRuleE } @Override - public TbQueueConsumer> createToCalculatedFieldNotificationsMsgConsumer() { + public TbQueueConsumer> createToCalculatedFieldNotificationMsgConsumer() { return new InMemoryTbQueueConsumer<>(storage, topicService.getCalculatedFieldNotificationsTopic(serviceInfoProvider.getServiceId()).getFullTopicName()); } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java index f07bd9dcbb..d688003115 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java @@ -542,7 +542,7 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi } @Override - public TbQueueConsumer> createToCalculatedFieldNotificationsMsgConsumer() { + public TbQueueConsumer> createToCalculatedFieldNotificationMsgConsumer() { TbKafkaConsumerTemplate.TbKafkaConsumerTemplateBuilder> consumerBuilder = TbKafkaConsumerTemplate.builder(); consumerBuilder.settings(kafkaSettings); consumerBuilder.topic(topicService.getCalculatedFieldNotificationsTopic(serviceInfoProvider.getServiceId()).getFullTopicName()); diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbRuleEngineQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbRuleEngineQueueFactory.java index d0e6c2f123..cb5c25141e 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbRuleEngineQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbRuleEngineQueueFactory.java @@ -337,7 +337,7 @@ public class KafkaTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory { } @Override - public TbQueueConsumer> createToCalculatedFieldNotificationsMsgConsumer() { + public TbQueueConsumer> createToCalculatedFieldNotificationMsgConsumer() { TbKafkaConsumerTemplate.TbKafkaConsumerTemplateBuilder> consumerBuilder = TbKafkaConsumerTemplate.builder(); consumerBuilder.settings(kafkaSettings); consumerBuilder.topic(topicService.getCalculatedFieldNotificationsTopic(serviceInfoProvider.getServiceId()).getFullTopicName()); @@ -349,6 +349,16 @@ public class KafkaTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory { return consumerBuilder.build(); } + @Override + public TbQueueProducer> createToCalculatedFieldNotificationMsgProducer() { + TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder> requestBuilder = TbKafkaProducerTemplate.builder(); + requestBuilder.settings(kafkaSettings); + requestBuilder.clientId("tb-calculated-field-notifications-producer-" + serviceInfoProvider.getServiceId()); + requestBuilder.defaultTopic(topicService.getCalculatedFieldNotificationsTopic(serviceInfoProvider.getServiceId()).getFullTopicName()); + requestBuilder.admin(notificationAdmin); + return requestBuilder.build(); + } + @Override public TbQueueConsumer> createCalculatedFieldStateConsumer() { return TbKafkaConsumerTemplate.>builder() diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineProducerProvider.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineProducerProvider.java index dcadf02d02..8e1952fc14 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineProducerProvider.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineProducerProvider.java @@ -50,6 +50,7 @@ public class TbRuleEngineProducerProvider implements TbQueueProducerProvider { private TbQueueProducer> toEdgeNotifications; private TbQueueProducer> toEdgeEvents; private TbQueueProducer> toCalculatedFields; + private TbQueueProducer> toCalculatedFieldNotifications; public TbRuleEngineProducerProvider(TbRuleEngineQueueFactory tbQueueProvider) { this.tbQueueProvider = tbQueueProvider; @@ -68,6 +69,7 @@ public class TbRuleEngineProducerProvider implements TbQueueProducerProvider { this.toEdgeNotifications = tbQueueProvider.createEdgeNotificationsMsgProducer(); this.toEdgeEvents = tbQueueProvider.createEdgeEventMsgProducer(); this.toCalculatedFields = tbQueueProvider.createToCalculatedFieldMsgProducer(); + this.toCalculatedFieldNotifications = tbQueueProvider.createToCalculatedFieldNotificationMsgProducer(); } @Override @@ -132,7 +134,7 @@ public class TbRuleEngineProducerProvider implements TbQueueProducerProvider { @Override public TbQueueProducer> getCalculatedFieldsNotificationsMsgProducer() { - throw new RuntimeException("Not Implemented! Should not be used by Rule Engine Service!"); + return toCalculatedFieldNotifications; } } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineQueueFactory.java index 03f662d8bb..329e3e346a 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineQueueFactory.java @@ -127,7 +127,9 @@ public interface TbRuleEngineQueueFactory extends TbUsageStatsClientQueueFactory TbQueueProducer> createToCalculatedFieldMsgProducer(); - TbQueueConsumer> createToCalculatedFieldNotificationsMsgConsumer(); + TbQueueConsumer> createToCalculatedFieldNotificationMsgConsumer(); + + TbQueueProducer> createToCalculatedFieldNotificationMsgProducer(); TbQueueConsumer> createCalculatedFieldStateConsumer(); diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java index 95fff48883..72792c1093 100644 --- a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java @@ -15,6 +15,7 @@ */ package org.thingsboard.script.api.tbel; +import com.fasterxml.jackson.databind.JsonNode; import com.google.common.primitives.Bytes; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.ArrayUtils; @@ -23,6 +24,10 @@ import org.mvel2.ParserConfiguration; import org.mvel2.execution.ExecutionArrayList; import org.mvel2.execution.ExecutionHashMap; import org.mvel2.util.MethodStub; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.common.util.geo.Coordinates; +import org.thingsboard.common.util.geo.GeoUtil; +import org.thingsboard.common.util.geo.RangeUnit; import org.thingsboard.server.common.data.StringUtils; import java.io.IOException; @@ -371,6 +376,10 @@ public class TbUtils { byte[].class, int.class))); parserConfig.addImport("parseBinaryArrayToInt", new MethodStub(TbUtils.class.getMethod("parseBinaryArrayToInt", byte[].class, int.class, int.class))); + parserConfig.addImport("isInsidePolygon", new MethodStub(TbUtils.class.getMethod("isInsidePolygon", + double.class, double.class, String.class))); + parserConfig.addImport("isInsideCircle", new MethodStub(TbUtils.class.getMethod("isInsideCircle", + double.class, double.class, String.class))); } public static String btoa(String input) { @@ -1437,6 +1446,22 @@ public class TbUtils { return result; } + public static boolean isInsidePolygon(double latitude, double longitude, String perimeter) { + return GeoUtil.contains(perimeter, new Coordinates(latitude, longitude)); + } + + public static boolean isInsideCircle(double latitude, double longitude, String perimeter) { + JsonNode perimeterJson = JacksonUtil.toJsonNode(perimeter); + double centerLatitude = Double.parseDouble(perimeterJson.get("latitude").asText()); + double centerLongitude = Double.parseDouble(perimeterJson.get("longitude").asText()); + double range = Double.parseDouble(perimeterJson.get("radius").asText()); + RangeUnit rangeUnit = perimeterJson.has("radiusUnit") ? RangeUnit.valueOf(perimeterJson.get("radiusUnit").asText()) : RangeUnit.METER; + + Coordinates entityCoordinates = new Coordinates(latitude, longitude); + Coordinates perimeterCoordinates = new Coordinates(centerLatitude, centerLongitude); + return range > GeoUtil.distance(entityCoordinates, perimeterCoordinates, rangeUnit); + } + private static byte isValidIntegerToByte(Integer val) { if (val > 255 || val < -128) { throw new NumberFormatException("The value '" + val + "' could not be correctly converted to a byte. " + diff --git a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java index e3860ed89a..1063a8e8de 100644 --- a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java +++ b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java @@ -1150,6 +1150,26 @@ public class TbUtilsTest { Assertions.assertTrue(TbUtils.isNaN(Double.NaN)); } + @Test + public void isInsidePolygon() { + // outside the polygon + String perimeter = "[[[50.75581142688204,29.097910166341073],[50.16785158177623,29.35066098977171],[50.164329922384674,29.773743889862114],[50.16785158177623,30.801230932938843],[50.459245308833495,30.92760634465418],[50.486522489629564,30.68548421850448],[50.703612031034005,30.872660513473573]],[[50.606017492632766,29.36165015600782],[50.54317104075835,29.762754723626013],[50.41021974600505,29.455058069014804]]]"; + Assertions.assertFalse(TbUtils.isInsidePolygon(50.50869555168039, 30.80123093293884, perimeter)); + // inside the polygon + Assertions.assertTrue(TbUtils.isInsidePolygon(50.50520628167696, 30.339685951022016, perimeter)); + // inside the hole + Assertions.assertFalse(TbUtils.isInsidePolygon(50.52265651287081, 29.488025567723156, perimeter)); + } + + @Test + public void isInsideCircle() { + // outside the circle + String perimeter = "{\"latitude\":50.32254778825905,\"longitude\":28.207787701215757,\"radius\":47477.33130420423}"; + Assertions.assertFalse(TbUtils.isInsideCircle(50.81490715736681, 28.05943395702824, perimeter)); + // inside the circle + Assertions.assertTrue(TbUtils.isInsideCircle(50.599397971892444, 28.086906872618542, perimeter)); + } + private static List toList(byte[] data) { List result = new ArrayList<>(data.length); for (Byte b : data) { diff --git a/common/util/pom.xml b/common/util/pom.xml index 430a6e7df9..82768cdb68 100644 --- a/common/util/pom.xml +++ b/common/util/pom.xml @@ -103,6 +103,14 @@ com.fasterxml.jackson.datatype jackson-datatype-jdk8 + + org.locationtech.spatial4j + spatial4j + + + org.locationtech.jts + jts-core + diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/Coordinates.java b/common/util/src/main/java/org/thingsboard/common/util/geo/Coordinates.java similarity index 94% rename from rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/Coordinates.java rename to common/util/src/main/java/org/thingsboard/common/util/geo/Coordinates.java index c3b91e39bb..dc1998bff1 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/Coordinates.java +++ b/common/util/src/main/java/org/thingsboard/common/util/geo/Coordinates.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.rule.engine.geo; +package org.thingsboard.common.util.geo; import lombok.Data; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/GeoUtil.java b/common/util/src/main/java/org/thingsboard/common/util/geo/GeoUtil.java similarity index 99% rename from rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/GeoUtil.java rename to common/util/src/main/java/org/thingsboard/common/util/geo/GeoUtil.java index f92a345362..11a6799348 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/GeoUtil.java +++ b/common/util/src/main/java/org/thingsboard/common/util/geo/GeoUtil.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.rule.engine.geo; +package org.thingsboard.common.util.geo; import com.google.gson.JsonArray; import com.google.gson.JsonElement; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/Perimeter.java b/common/util/src/main/java/org/thingsboard/common/util/geo/Perimeter.java similarity index 95% rename from rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/Perimeter.java rename to common/util/src/main/java/org/thingsboard/common/util/geo/Perimeter.java index 3474683c6c..238fa19af5 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/Perimeter.java +++ b/common/util/src/main/java/org/thingsboard/common/util/geo/Perimeter.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.rule.engine.geo; +package org.thingsboard.common.util.geo; import lombok.Data; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/PerimeterType.java b/common/util/src/main/java/org/thingsboard/common/util/geo/PerimeterType.java similarity index 94% rename from rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/PerimeterType.java rename to common/util/src/main/java/org/thingsboard/common/util/geo/PerimeterType.java index 2df6cf796c..c3b334abd7 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/PerimeterType.java +++ b/common/util/src/main/java/org/thingsboard/common/util/geo/PerimeterType.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.rule.engine.geo; +package org.thingsboard.common.util.geo; public enum PerimeterType { CIRCLE, POLYGON diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/RangeUnit.java b/common/util/src/main/java/org/thingsboard/common/util/geo/RangeUnit.java similarity index 95% rename from rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/RangeUnit.java rename to common/util/src/main/java/org/thingsboard/common/util/geo/RangeUnit.java index 090d251b33..56883ca43f 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/RangeUnit.java +++ b/common/util/src/main/java/org/thingsboard/common/util/geo/RangeUnit.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.rule.engine.geo; +package org.thingsboard.common.util.geo; public enum RangeUnit { METER(1000.0), KILOMETER(1.0), FOOT(3280.84), MILE(0.62137), NAUTICAL_MILE(0.539957); diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/CalculatedFieldDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/CalculatedFieldDataValidator.java index 187de20667..c9c7af1a89 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/CalculatedFieldDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/CalculatedFieldDataValidator.java @@ -39,6 +39,7 @@ public class CalculatedFieldDataValidator extends DataValidator protected void validateCreate(TenantId tenantId, CalculatedField calculatedField) { validateNumberOfCFsPerEntity(tenantId, calculatedField.getEntityId()); validateNumberOfArgumentsPerCF(tenantId, calculatedField); + validateArgumentNames(calculatedField); } @Override @@ -48,6 +49,7 @@ public class CalculatedFieldDataValidator extends DataValidator throw new DataValidationException("Can't update non existing calculated field!"); } validateNumberOfArgumentsPerCF(tenantId, calculatedField); + validateArgumentNames(calculatedField); return old; } @@ -71,4 +73,10 @@ public class CalculatedFieldDataValidator extends DataValidator } } + private void validateArgumentNames(CalculatedField calculatedField) { + if (calculatedField.getConfiguration().getArguments().containsKey("ctx")) { + throw new DataValidationException("Argument name 'ctx' is reserved and cannot be used."); + } + } + } diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/cf/CalculatedFieldTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/cf/CalculatedFieldTest.java index 63faed41ab..2593e18a55 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/cf/CalculatedFieldTest.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/cf/CalculatedFieldTest.java @@ -34,6 +34,7 @@ import org.thingsboard.server.common.data.cf.configuration.ArgumentType; import org.thingsboard.server.common.data.cf.configuration.Output; import org.thingsboard.server.common.data.cf.configuration.OutputType; import org.thingsboard.server.common.data.cf.configuration.ReferencedEntityKey; +import org.thingsboard.server.common.data.cf.configuration.ScriptCalculatedFieldConfiguration; import org.thingsboard.server.common.data.cf.configuration.SimpleCalculatedFieldConfiguration; import org.thingsboard.server.common.data.debug.DebugSettings; import org.thingsboard.server.common.data.device.data.DefaultDeviceConfiguration; @@ -73,7 +74,7 @@ public class CalculatedFieldTest extends AbstractContainerTest { " var airDensity = pressure / (287.05 * temperatureK);\n" + "\n" + " return {\n" + - " \"airDensity\": airDensity\n" + + " \"airDensity\": toFixed(airDensity, 2)\n" + " };"; private TenantId tenantId; @@ -280,6 +281,34 @@ public class CalculatedFieldTest extends AbstractContainerTest { testRestClient.deleteCalculatedFieldIfExists(savedCalculatedField.getId()); } + @Test + public void testEntityIdIsProfileAndRefEntityIsCommon() { + // login tenant admin + testRestClient.getAndSetUserToken(tenantAdminId); + + CalculatedField savedCalculatedField = createScriptCalculatedField(deviceProfileId); + + await().alias("create CF -> perform initial calculation for device by profile").atMost(TIMEOUT, TimeUnit.SECONDS) + .pollInterval(POLL_INTERVAL, TimeUnit.SECONDS) + .untilAsserted(() -> { + JsonNode airDensity = testRestClient.getLatestTelemetry(device.getId()); + assertThat(airDensity).isNotNull(); + assertThat(airDensity.get("airDensity").get(0).get("value").asText()).isEqualTo("1.05"); + }); + + testRestClient.postTelemetryAttribute(asset.getId(), DataConstants.SERVER_SCOPE, JacksonUtil.toJsonNode("{\"altitude\":1531}")); + + await().alias("create CF -> update telemetry for common entity").atMost(TIMEOUT, TimeUnit.SECONDS) + .pollInterval(POLL_INTERVAL, TimeUnit.SECONDS) + .untilAsserted(() -> { + JsonNode airDensity = testRestClient.getLatestTelemetry(device.getId()); + assertThat(airDensity).isNotNull(); + assertThat(airDensity.get("airDensity").get(0).get("value").asText()).isEqualTo("0.99"); + }); + + testRestClient.deleteCalculatedFieldIfExists(savedCalculatedField.getId()); + } + private CalculatedField createSimpleCalculatedField() { return createSimpleCalculatedField(device.getId()); } @@ -323,19 +352,21 @@ public class CalculatedFieldTest extends AbstractContainerTest { calculatedField.setName("Air density" + RandomStringUtils.randomAlphabetic(5)); calculatedField.setDebugSettings(DebugSettings.all()); - SimpleCalculatedFieldConfiguration config = new SimpleCalculatedFieldConfiguration(); + ScriptCalculatedFieldConfiguration config = new ScriptCalculatedFieldConfiguration(); Argument argument1 = new Argument(); argument1.setRefEntityId(asset.getId()); ReferencedEntityKey refEntityKey1 = new ReferencedEntityKey("altitude", ArgumentType.ATTRIBUTE, AttributeScope.SERVER_SCOPE); argument1.setRefEntityKey(refEntityKey1); - config.setArguments(Map.of("altitude", argument1)); Argument argument2 = new Argument(); ReferencedEntityKey refEntityKey2 = new ReferencedEntityKey("temperatureInF", ArgumentType.TS_ROLLING, null); + argument2.setTimeWindow(30000L); + argument2.setLimit(5); argument2.setRefEntityKey(refEntityKey2); - config.setArguments(Map.of("temperature", argument2)); - config.setExpression("return {\"airDensity\": 5};"); + config.setArguments(Map.of("altitude", argument1, "temperature", argument2)); + + config.setExpression(exampleScript); Output output = new Output(); output.setType(OutputType.TIME_SERIES); diff --git a/rule-engine/rule-engine-components/pom.xml b/rule-engine/rule-engine-components/pom.xml index eb568f9cc7..7ee5ab1496 100644 --- a/rule-engine/rule-engine-components/pom.xml +++ b/rule-engine/rule-engine-components/pom.xml @@ -120,14 +120,6 @@ org.bouncycastle bcpkix-jdk18on - - org.locationtech.spatial4j - spatial4j - - - org.locationtech.jts - jts-core - com.sun.mail jakarta.mail diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/AbstractGeofencingNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/AbstractGeofencingNode.java index 8025baeec7..ff6b3acb88 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/AbstractGeofencingNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/AbstractGeofencingNode.java @@ -21,6 +21,11 @@ import com.google.gson.JsonParser; import org.locationtech.spatial4j.context.jts.JtsSpatialContext; import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.common.util.geo.Coordinates; +import org.thingsboard.common.util.geo.GeoUtil; +import org.thingsboard.common.util.geo.Perimeter; +import org.thingsboard.common.util.geo.PerimeterType; +import org.thingsboard.common.util.geo.RangeUnit; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; @@ -74,7 +79,7 @@ public abstract class AbstractGeofencingNode