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 020fa7f61e..1f481ea0ea 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 @@ -243,7 +243,7 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM } if (state instanceof PropagationCalculatedFieldState propagationState) { PropagationArgumentEntry entry = new PropagationArgumentEntry(); - entry.setAdded(msg.getRelatedEntityId()); + entry.setAdded(List.of(msg.getRelatedEntityId())); updatedArgs = propagationState.update(Map.of(PROPAGATION_CONFIG_ARGUMENT, entry), ctx); } if (CollectionsUtil.isEmpty(updatedArgs)) { @@ -422,19 +422,7 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM if (state == null) { state = createState(ctx); justRestored = true; - } else if (ctx.shouldFetchRelationQueryDynamicArgumentsFromDb(state)) { - log.debug("[{}][{}] Going to update dynamic arguments for CF.", entityId, ctx.getCfId()); - try { - Map dynamicArgsFromDb = cfService.fetchDynamicArgsFromDb(ctx, entityId); - dynamicArgsFromDb.forEach(newArgValues::putIfAbsent); - if (ctx.getCfType() == CalculatedFieldType.GEOFENCING) { - var geofencingState = (GeofencingCalculatedFieldState) state; - geofencingState.updateLastDynamicArgumentsRefreshTs(); - } - } catch (Exception e) { - throw CalculatedFieldException.builder().ctx(ctx).eventEntity(entityId).cause(e).build(); - } - } else if (ctx.shouldFetchEntityRelations(state)) { + } else if (ctx.shouldFetchRelatedEntities(state)) { log.debug("[{}][{}] Going to update related entities for CF.", entityId, ctx.getCfId()); try { if (state instanceof RelatedEntitiesAggregationCalculatedFieldState relatedEntitiesState) { @@ -448,6 +436,11 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM justRestored = true; } } + if (state instanceof GeofencingCalculatedFieldState geofencingCalculatedFieldState) { + Map dynamicArgsFromDb = cfService.fetchDynamicArgsFromDb(ctx, entityId); + dynamicArgsFromDb.forEach(newArgValues::putIfAbsent); + geofencingCalculatedFieldState.updateScheduledRefreshTs(); + } } catch (Exception e) { throw CalculatedFieldException.builder().ctx(ctx).eventEntity(entityId).cause(e).build(); } @@ -477,9 +470,9 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM state.setCtx(ctx, actorCtx); state.init(false); - if (ctx.getCfType() == CalculatedFieldType.GEOFENCING && ctx.isRelationQueryDynamicArguments()) { + if (ctx.getCfType() == CalculatedFieldType.GEOFENCING && ctx.isCfHasRelationPathQuerySource()) { GeofencingCalculatedFieldState geofencingState = (GeofencingCalculatedFieldState) state; - geofencingState.updateLastDynamicArgumentsRefreshTs(); + geofencingState.updateScheduledRefreshTs(); } Map arguments = fetchArguments(ctx); diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 83603f7ea5..136a101e93 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -150,8 +150,6 @@ import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.domain.DomainService; import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.entityview.EntityViewService; -import org.thingsboard.server.dao.exception.DataValidationException; -import org.thingsboard.server.dao.exception.EntitiesLimitExceededException; import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.job.JobService; import org.thingsboard.server.dao.mobile.MobileAppBundleService; @@ -175,6 +173,8 @@ import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.widget.WidgetTypeService; import org.thingsboard.server.dao.widget.WidgetsBundleService; +import org.thingsboard.server.exception.DataValidationException; +import org.thingsboard.server.exception.EntitiesLimitExceededException; import org.thingsboard.server.exception.ThingsboardErrorResponseHandler; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; diff --git a/application/src/main/java/org/thingsboard/server/controller/EdgeController.java b/application/src/main/java/org/thingsboard/server/controller/EdgeController.java index 1b3e494802..df9e5c6476 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EdgeController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EdgeController.java @@ -54,7 +54,7 @@ import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportReques import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportResult; import org.thingsboard.server.common.msg.edge.FromEdgeSyncResponse; import org.thingsboard.server.config.annotations.ApiOperation; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.queue.util.TbCoreComponent; diff --git a/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java b/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java index 3ef24e2db9..8cabf2ed7b 100644 --- a/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java +++ b/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java @@ -52,7 +52,6 @@ import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.msg.tools.MaxPayloadSizeExceededException; import org.thingsboard.server.common.msg.tools.TbRateLimitsException; -import org.thingsboard.server.dao.exception.EntitiesLimitExceededException; import org.thingsboard.server.service.security.exception.AuthMethodNotSupportedException; import org.thingsboard.server.service.security.exception.JwtExpiredTokenException; import org.thingsboard.server.service.security.exception.UserPasswordExpiredException; diff --git a/application/src/main/java/org/thingsboard/server/service/cf/AbstractCalculatedFieldProcessingService.java b/application/src/main/java/org/thingsboard/server/service/cf/AbstractCalculatedFieldProcessingService.java index 13ceb406bb..ec645085e6 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/AbstractCalculatedFieldProcessingService.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/AbstractCalculatedFieldProcessingService.java @@ -22,17 +22,18 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; import com.google.gson.JsonElement; -import com.google.gson.JsonParser; import jakarta.annotation.PostConstruct; import jakarta.annotation.PreDestroy; import lombok.Data; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.function.TriConsumer; import org.thingsboard.common.util.DonAsynchron; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.rule.engine.api.AttributesSaveRequest; import org.thingsboard.rule.engine.api.AttributesSaveRequest.Strategy; import org.thingsboard.rule.engine.api.TimeseriesSaveRequest; +import org.thingsboard.server.actors.calculatedField.MultipleTbCallback; import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.adaptor.JsonConverter; import org.thingsboard.server.common.data.AttributeScope; @@ -80,7 +81,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.function.Function; @@ -391,6 +391,24 @@ public abstract class AbstractCalculatedFieldProcessingService { return new BaseReadTsKvQuery(argument.getRefEntityKey().getKey(), startTs, endTs, 0, limit, Aggregation.NONE); } + protected void handlePropagationResults(PropagationCalculatedFieldResult propagationResult, TbCallback callback, + TriConsumer telemetryResultHandler) { + List propagationEntityIds = propagationResult.getEntityIds(); + if (propagationEntityIds.isEmpty()) { + callback.onSuccess(); + return; + } + if (propagationEntityIds.size() == 1) { + EntityId propagationEntityId = propagationEntityIds.get(0); + telemetryResultHandler.accept(propagationEntityId, propagationResult.getResult(), callback); + return; + } + MultipleTbCallback multipleTbCallback = new MultipleTbCallback(propagationEntityIds.size(), callback); + for (var propagationEntityId : propagationEntityIds) { + telemetryResultHandler.accept(propagationEntityId, propagationResult.getResult(), multipleTbCallback); + } + } + protected void sendMsgToRuleEngine(TenantId tenantId, EntityId entityId, TbCallback callback, TbMsg msg) { try { clusterService.pushMsgToRuleEngine(tenantId, entityId, msg, new TbQueueCallback() { @@ -413,7 +431,7 @@ public abstract class AbstractCalculatedFieldProcessingService { protected void saveTelemetryResult(TenantId tenantId, EntityId entityId, String cfName, TelemetryCalculatedFieldResult cfResult, List cfIds, TbCallback callback) { OutputType type = cfResult.getType(); - JsonElement jsonResult = JsonParser.parseString(Objects.requireNonNull(cfResult.stringValue())); + JsonElement jsonResult = cfResult.toJsonElement(); log.trace("[{}][{}] Saving CF result: {}", tenantId, entityId, jsonResult); switch (type) { diff --git a/application/src/main/java/org/thingsboard/server/service/cf/CalculatedFieldProcessingService.java b/application/src/main/java/org/thingsboard/server/service/cf/CalculatedFieldProcessingService.java index 53d64e5b27..13fad720b3 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/CalculatedFieldProcessingService.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/CalculatedFieldProcessingService.java @@ -27,9 +27,11 @@ import org.thingsboard.server.service.cf.ctx.CalculatedFieldEntityCtxId; import org.thingsboard.server.service.cf.ctx.state.ArgumentEntry; import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldCtx; import org.thingsboard.server.service.cf.ctx.state.aggregation.single.AggIntervalEntry; +import org.thingsboard.server.service.cf.ctx.state.propagation.PropagationArgumentEntry; import java.util.List; import java.util.Map; +import java.util.Optional; public interface CalculatedFieldProcessingService { @@ -37,6 +39,8 @@ public interface CalculatedFieldProcessingService { Map fetchDynamicArgsFromDb(CalculatedFieldCtx ctx, EntityId entityId); + Optional fetchPropagationArgumentFromDb(CalculatedFieldCtx ctx, EntityId entityId); + List fetchRelatedEntities(CalculatedFieldCtx ctx, EntityId entityId); Map fetchArgsFromDb(TenantId tenantId, EntityId entityId, Map arguments); diff --git a/application/src/main/java/org/thingsboard/server/service/cf/CalculatedFieldResult.java b/application/src/main/java/org/thingsboard/server/service/cf/CalculatedFieldResult.java index c973cebc18..8b4c2a0101 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/CalculatedFieldResult.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/CalculatedFieldResult.java @@ -15,11 +15,14 @@ */ package org.thingsboard.server.service.cf; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; import org.thingsboard.server.common.data.id.CalculatedFieldId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.msg.TbMsg; import java.util.List; +import java.util.Objects; public interface CalculatedFieldResult { @@ -29,4 +32,8 @@ public interface CalculatedFieldResult { boolean isEmpty(); + default JsonElement toJsonElement() { + return JsonParser.parseString(Objects.requireNonNull(stringValue())); + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/cf/DefaultCalculatedFieldProcessingService.java b/application/src/main/java/org/thingsboard/server/service/cf/DefaultCalculatedFieldProcessingService.java index 271fdb828d..1ab8f5a076 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/DefaultCalculatedFieldProcessingService.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/DefaultCalculatedFieldProcessingService.java @@ -17,13 +17,13 @@ package org.thingsboard.server.service.cf; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.function.TriConsumer; import org.springframework.stereotype.Service; import org.thingsboard.server.actors.calculatedField.CalculatedFieldTelemetryMsg; import org.thingsboard.server.actors.calculatedField.MultipleTbCallback; import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.cf.CalculatedFieldType; import org.thingsboard.server.common.data.cf.configuration.Argument; import org.thingsboard.server.common.data.cf.configuration.aggregation.AggMetric; import org.thingsboard.server.common.data.cf.configuration.aggregation.RelatedEntitiesAggregationCalculatedFieldConfiguration; @@ -50,6 +50,7 @@ import org.thingsboard.server.service.cf.ctx.CalculatedFieldEntityCtxId; import org.thingsboard.server.service.cf.ctx.state.ArgumentEntry; import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldCtx; import org.thingsboard.server.service.cf.ctx.state.aggregation.single.AggIntervalEntry; +import org.thingsboard.server.service.cf.ctx.state.propagation.PropagationArgumentEntry; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import java.util.ArrayList; @@ -57,6 +58,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.UUID; import java.util.concurrent.ExecutionException; @@ -94,11 +96,18 @@ public class DefaultCalculatedFieldProcessingService extends AbstractCalculatedF @Override public Map fetchDynamicArgsFromDb(CalculatedFieldCtx ctx, EntityId entityId) { - return switch (ctx.getCfType()) { - case GEOFENCING -> resolveArgumentFutures(fetchGeofencingCalculatedFieldArguments(ctx, entityId, true, System.currentTimeMillis())); - case PROPAGATION -> resolveArgumentFutures(Map.of(PROPAGATION_CONFIG_ARGUMENT, fetchPropagationCalculatedFieldArgument(ctx, entityId))); - default -> Collections.emptyMap(); - }; + return ctx.getCfType() == CalculatedFieldType.GEOFENCING ? + resolveArgumentFutures(fetchGeofencingCalculatedFieldArguments(ctx, entityId, true, System.currentTimeMillis())) : + Collections.emptyMap(); + } + + @Override + public Optional fetchPropagationArgumentFromDb(CalculatedFieldCtx ctx, EntityId entityId) { + if (ctx.getCfType() != CalculatedFieldType.PROPAGATION) { + return Optional.empty(); + } + return Optional.of((PropagationArgumentEntry) + resolveArgumentValue(PROPAGATION_CONFIG_ARGUMENT, fetchPropagationCalculatedFieldArgument(ctx, entityId))); } @Override @@ -169,24 +178,6 @@ public class DefaultCalculatedFieldProcessingService extends AbstractCalculatedF sendMsgToRuleEngine(tenantId, entityId, callback, result.toTbMsg(entityId, cfName, cfIds)); } - private void handlePropagationResults(PropagationCalculatedFieldResult propagationResult, TbCallback callback, - TriConsumer telemetryResultHandler) { - List propagationEntityIds = propagationResult.getEntityIds(); - if (propagationEntityIds.isEmpty()) { - callback.onSuccess(); - return; - } - if (propagationEntityIds.size() == 1) { - EntityId propagationEntityId = propagationEntityIds.get(0); - telemetryResultHandler.accept(propagationEntityId, propagationResult.getResult(), callback); - return; - } - MultipleTbCallback multipleTbCallback = new MultipleTbCallback(propagationEntityIds.size(), callback); - for (var propagationEntityId : propagationEntityIds) { - telemetryResultHandler.accept(propagationEntityId, propagationResult.getResult(), multipleTbCallback); - } - } - @Override public void pushMsgToLinks(CalculatedFieldTelemetryMsg msg, List linkedCalculatedFields, TbCallback callback) { Map> unicasts = new HashMap<>(); diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/BaseCalculatedFieldState.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/BaseCalculatedFieldState.java index 5173c48892..754fc2f6ee 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/BaseCalculatedFieldState.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/BaseCalculatedFieldState.java @@ -63,7 +63,7 @@ public abstract class BaseCalculatedFieldState implements CalculatedFieldState, this.ctx = ctx; this.actorCtx = actorCtx; this.requiredArguments = ctx.getArgNames(); - this.readinessStatus = checkReadiness(requiredArguments, arguments); + this.readinessStatus = checkReadiness(); } @Override @@ -108,7 +108,7 @@ public abstract class BaseCalculatedFieldState implements CalculatedFieldState, if (updatedArguments == null) { return Collections.emptyMap(); } - readinessStatus = checkReadiness(requiredArguments, arguments); + readinessStatus = checkReadiness(); return updatedArguments; } @@ -183,13 +183,13 @@ public abstract class BaseCalculatedFieldState implements CalculatedFieldState, return latestTs; } - protected ReadinessStatus checkReadiness(List requiredArguments, Map currentArguments) { - if (currentArguments == null) { + protected ReadinessStatus checkReadiness() { + if (arguments == null) { return ReadinessStatus.from(requiredArguments); } List emptyArguments = null; for (String requiredArgumentKey : requiredArguments) { - ArgumentEntry argumentEntry = currentArguments.get(requiredArgumentKey); + ArgumentEntry argumentEntry = arguments.get(requiredArgumentKey); if (argumentEntry == null || argumentEntry.isEmpty()) { if (emptyArguments == null) { emptyArguments = new ArrayList<>(); 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 b97be4dcd8..50ee88cae6 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 @@ -63,8 +63,7 @@ import org.thingsboard.server.dao.util.TimeUtils; import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldTelemetryMsgProto; import org.thingsboard.server.service.cf.CalculatedFieldProcessingService; import org.thingsboard.server.service.cf.ctx.CalculatedFieldEntityCtxId; -import org.thingsboard.server.service.cf.ctx.state.aggregation.RelatedEntitiesAggregationCalculatedFieldState; -import org.thingsboard.server.service.cf.ctx.state.geofencing.GeofencingCalculatedFieldState; +import org.thingsboard.server.service.cf.ctx.state.geofencing.ScheduledRefreshSupported; import org.thingsboard.server.service.telemetry.AlarmSubscriptionService; import java.io.Closeable; @@ -122,14 +121,14 @@ public class CalculatedFieldCtx implements Closeable { private long maxSingleValueArgumentSize; private long intermediateAggregationIntervalMillis; - private boolean relationQueryDynamicArguments; + private boolean cfHasRelationPathQuerySource; private List mainEntityGeofencingArgumentNames; private List linkedEntityAndCurrentOwnerGeofencingArgumentNames; private List relatedEntityArgumentNames; private long scheduledUpdateIntervalMillis; - private long cfCheckReevaluationInterval; - private long alarmReevaluationInterval; + private long cfCheckReevaluationIntervalMillis; + private long alarmReevaluationIntervalMillis; private Argument propagationArgument; private boolean applyExpressionForResolvedArguments; @@ -161,10 +160,11 @@ public class CalculatedFieldCtx implements Closeable { if (refId == null) { if (CalculatedFieldType.RELATED_ENTITIES_AGGREGATION.equals(cfType)) { relatedEntityArguments.compute(refKey, (key, existingNames) -> CollectionsUtil.addToSet(existingNames, entry.getKey())); + cfHasRelationPathQuerySource = true; continue; } if (entry.getValue().hasRelationQuerySource()) { - relationQueryDynamicArguments = true; + cfHasRelationPathQuerySource = true; continue; } if (entry.getValue().hasOwnerSource()) { @@ -201,7 +201,7 @@ public class CalculatedFieldCtx implements Closeable { if (calculatedField.getConfiguration() instanceof PropagationCalculatedFieldConfiguration propagationConfig) { propagationArgument = propagationConfig.toPropagationArgument(); applyExpressionForResolvedArguments = propagationConfig.isApplyExpressionToResolvedArguments(); - relationQueryDynamicArguments = true; + cfHasRelationPathQuerySource = true; } } if (calculatedField.getConfiguration() instanceof ScheduledUpdateSupportedCalculatedFieldConfiguration scheduledConfig) { @@ -246,8 +246,7 @@ public class CalculatedFieldCtx implements Closeable { boolean requiresScheduledReevaluation = calculatedField.getConfiguration().requiresScheduledReevaluation(); if (calculatedField.getConfiguration() instanceof AlarmCalculatedFieldConfiguration) { if (requiresScheduledReevaluation) { - long reevaluationIntervalMillis = TimeUnit.SECONDS.toMillis(alarmReevaluationInterval); - if (now - lastReevaluationTs >= reevaluationIntervalMillis) { + if (now - lastReevaluationTs >= alarmReevaluationIntervalMillis) { lastReevaluationTs = now; return true; } @@ -306,8 +305,8 @@ public class CalculatedFieldCtx implements Closeable { this.maxStateSize = apiLimitService.getLimit(tenantId, DefaultTenantProfileConfiguration::getMaxStateSizeInKBytes) * 1024; this.maxSingleValueArgumentSize = apiLimitService.getLimit(tenantId, DefaultTenantProfileConfiguration::getMaxSingleValueArgumentSizeInKBytes) * 1024; this.intermediateAggregationIntervalMillis = TimeUnit.SECONDS.toMillis(apiLimitService.getLimit(tenantId, DefaultTenantProfileConfiguration::getIntermediateAggregationIntervalInSecForCF)); - this.cfCheckReevaluationInterval = apiLimitService.getLimit(tenantId, DefaultTenantProfileConfiguration::getCfReevaluationCheckInterval); - this.alarmReevaluationInterval = apiLimitService.getLimit(tenantId, DefaultTenantProfileConfiguration::getAlarmsReevaluationInterval); + this.cfCheckReevaluationIntervalMillis = TimeUnit.SECONDS.toMillis(apiLimitService.getLimit(tenantId, DefaultTenantProfileConfiguration::getCfReevaluationCheckInterval)); + this.alarmReevaluationIntervalMillis = TimeUnit.SECONDS.toMillis(apiLimitService.getLimit(tenantId, DefaultTenantProfileConfiguration::getAlarmsReevaluationInterval)); } public double evaluateSimpleExpression(Expression expression, CalculatedFieldState state) { @@ -757,38 +756,20 @@ public class CalculatedFieldCtx implements Closeable { return scheduledUpdateIntervalMillis == DISABLED_INTERVAL_VALUE; } - public boolean shouldFetchRelationQueryDynamicArgumentsFromDb(CalculatedFieldState state) { - if (!relationQueryDynamicArguments) { + public boolean shouldFetchRelatedEntities(CalculatedFieldState state) { + if (!cfHasRelationPathQuerySource) { return false; } - return switch (cfType) { - case PROPAGATION -> true; - case GEOFENCING -> { - if (isScheduledUpdateDisabled()) { - yield false; - } - var geofencingState = (GeofencingCalculatedFieldState) state; - if (geofencingState.getLastDynamicArgumentsRefreshTs() == DEFAULT_LAST_UPDATE_TS) { - yield true; - } - yield geofencingState.getLastDynamicArgumentsRefreshTs() < - System.currentTimeMillis() - scheduledUpdateIntervalMillis; - } - default -> false; - }; - } - - public boolean shouldFetchEntityRelations(CalculatedFieldState state) { - if (!(state instanceof RelatedEntitiesAggregationCalculatedFieldState relatedEntitiesAggState)) { + if (isScheduledUpdateDisabled()) { return false; } - if (isScheduledUpdateDisabled()) { + if (!(state instanceof ScheduledRefreshSupported scheduledRefreshSupported)) { return false; } - if (relatedEntitiesAggState.getLastRelatedEntitiesRefreshTs() == DEFAULT_LAST_UPDATE_TS) { + if (scheduledRefreshSupported.getLastScheduledRefreshTs() == DEFAULT_LAST_UPDATE_TS) { return true; } - return relatedEntitiesAggState.getLastRelatedEntitiesRefreshTs() < System.currentTimeMillis() - scheduledUpdateIntervalMillis; + return scheduledRefreshSupported.getLastScheduledRefreshTs() < System.currentTimeMillis() - scheduledUpdateIntervalMillis; } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldState.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldState.java index f254631491..df0999922e 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldState.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldState.java @@ -110,7 +110,13 @@ public interface CalculatedFieldState extends Closeable { private static final String MISSING_PROPAGATION_TARGETS_ERROR = "No entities found via 'Propagation path to related entities'. " + "Verify the configured relation type and direction."; private static final String MISSING_PROPAGATION_TARGETS_AND_ARGUMENTS_ERROR = MISSING_PROPAGATION_TARGETS_ERROR + " Missing arguments to propagate: "; - private static final ReadinessStatus READY = new ReadinessStatus(true, null); + public static final String MISSING_AGGREGATION_ENTITIES_ERROR = "No entities found via 'Aggregation path to related entities'. " + + "Verify the configured relation type and direction."; + public static final ReadinessStatus READY = new ReadinessStatus(true, null); + + public static ReadinessStatus notReady(String errorMsg) { + return new ReadinessStatus(false, errorMsg); + } public static ReadinessStatus from(List emptyOrMissingArguments) { if (CollectionsUtil.isEmpty(emptyOrMissingArguments)) { @@ -118,13 +124,12 @@ public interface CalculatedFieldState extends Closeable { } boolean propagationCtxIsEmpty = emptyOrMissingArguments.remove(PROPAGATION_CONFIG_ARGUMENT); if (!propagationCtxIsEmpty) { - return new ReadinessStatus(false, MISSING_REQUIRED_ARGUMENTS_ERROR + String.join(", ", emptyOrMissingArguments)); + return notReady(MISSING_REQUIRED_ARGUMENTS_ERROR + String.join(", ", emptyOrMissingArguments)); } if (emptyOrMissingArguments.isEmpty()) { - return new ReadinessStatus(false, MISSING_PROPAGATION_TARGETS_ERROR); + return notReady(MISSING_PROPAGATION_TARGETS_ERROR); } - return new ReadinessStatus(false, MISSING_PROPAGATION_TARGETS_AND_ARGUMENTS_ERROR + - String.join(", ", emptyOrMissingArguments)); + return notReady(MISSING_PROPAGATION_TARGETS_AND_ARGUMENTS_ERROR + String.join(", ", emptyOrMissingArguments)); } } diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/RelatedEntitiesAggregationCalculatedFieldState.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/RelatedEntitiesAggregationCalculatedFieldState.java index 7036e4bd77..1edfea70a3 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/RelatedEntitiesAggregationCalculatedFieldState.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/RelatedEntitiesAggregationCalculatedFieldState.java @@ -41,6 +41,7 @@ import org.thingsboard.server.service.cf.ctx.state.ArgumentEntryType; import org.thingsboard.server.service.cf.ctx.state.BaseCalculatedFieldState; import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldCtx; import org.thingsboard.server.service.cf.ctx.state.aggregation.function.AggEntry; +import org.thingsboard.server.service.cf.ctx.state.geofencing.ScheduledRefreshSupported; import java.util.ArrayList; import java.util.HashMap; @@ -52,16 +53,17 @@ import java.util.stream.Collectors; import static java.util.concurrent.TimeUnit.SECONDS; import static org.thingsboard.server.service.cf.ctx.state.CalculatedFieldCtx.DISABLED_INTERVAL_VALUE; +import static org.thingsboard.server.service.cf.ctx.state.CalculatedFieldState.ReadinessStatus.MISSING_AGGREGATION_ENTITIES_ERROR; @Slf4j -@Getter -public class RelatedEntitiesAggregationCalculatedFieldState extends BaseCalculatedFieldState { +public class RelatedEntitiesAggregationCalculatedFieldState extends BaseCalculatedFieldState implements ScheduledRefreshSupported { @Setter + @Getter private long lastArgsRefreshTs = DEFAULT_LAST_UPDATE_TS; @Setter + @Getter private long lastMetricsEvalTs = DEFAULT_LAST_UPDATE_TS; - @Setter private long lastRelatedEntitiesRefreshTs = DEFAULT_LAST_UPDATE_TS; private long deduplicationIntervalMs = DISABLED_INTERVAL_VALUE; private Map metrics; @@ -103,13 +105,24 @@ public class RelatedEntitiesAggregationCalculatedFieldState extends BaseCalculat @Override public void reset() { // must reset everything dependent on arguments super.reset(); + resetScheduledRefreshTs(); lastArgsRefreshTs = DEFAULT_LAST_UPDATE_TS; lastMetricsEvalTs = DEFAULT_LAST_UPDATE_TS; - lastRelatedEntitiesRefreshTs = DEFAULT_LAST_UPDATE_TS; metrics = null; } - public void updateLastRelatedEntitiesRefreshTs() { + @Override + public void resetScheduledRefreshTs() { + lastRelatedEntitiesRefreshTs = DEFAULT_LAST_UPDATE_TS; + } + + @Override + public long getLastScheduledRefreshTs() { + return lastRelatedEntitiesRefreshTs; + } + + @Override + public void updateScheduledRefreshTs() { lastRelatedEntitiesRefreshTs = System.currentTimeMillis(); } @@ -127,7 +140,7 @@ public class RelatedEntitiesAggregationCalculatedFieldState extends BaseCalculat public List checkRelatedEntities(List relatedEntities) { Map> entityInputs = prepareInputs(); findOutdatedEntities(entityInputs, relatedEntities).forEach(this::cleanupEntityData); - updateLastRelatedEntitiesRefreshTs(); + updateScheduledRefreshTs(); return findMissingEntities(entityInputs, relatedEntities); } @@ -165,6 +178,7 @@ public class RelatedEntitiesAggregationCalculatedFieldState extends BaseCalculat }); lastMetricsEvalTs = DEFAULT_LAST_UPDATE_TS; lastArgsRefreshTs = System.currentTimeMillis(); + readinessStatus = checkReadiness(); } public void scheduleReevaluation() { @@ -276,4 +290,18 @@ public class RelatedEntitiesAggregationCalculatedFieldState extends BaseCalculat record EntityArgument(EntityInfo entity, JsonNode entityArguments) {} + @Override + protected ReadinessStatus checkReadiness() { + if (arguments == null) { + return ReadinessStatus.notReady(MISSING_AGGREGATION_ENTITIES_ERROR); + } + for (String requiredArgumentKey : requiredArguments) { + ArgumentEntry argumentEntry = arguments.get(requiredArgumentKey); + if (argumentEntry == null || argumentEntry.isEmpty()) { + return ReadinessStatus.notReady(MISSING_AGGREGATION_ENTITIES_ERROR); + } + } + return ReadinessStatus.READY; + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/single/EntityAggregationCalculatedFieldState.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/single/EntityAggregationCalculatedFieldState.java index 721e93c6fc..0f6e01a344 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/single/EntityAggregationCalculatedFieldState.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/aggregation/single/EntityAggregationCalculatedFieldState.java @@ -205,7 +205,7 @@ public class EntityAggregationCalculatedFieldState extends BaseCalculatedFieldSt handleExpiredInterval(intervalEntry, args, results); expiredIntervals.add(intervalEntry); } else if (now - startTs >= intervalEntry.getIntervalDuration()) { - handleActiveInterval(ctx.getCfCheckReevaluationInterval(), intervalEntry, args, results); + handleActiveInterval(ctx.getCfCheckReevaluationIntervalMillis(), intervalEntry, args, results); if (watermarkDuration == 0) { expiredIntervals.add(intervalEntry); } @@ -288,7 +288,7 @@ public class EntityAggregationCalculatedFieldState extends BaseCalculatedFieldSt } if (!metricsNode.isEmpty()) { ObjectNode resultNode = JacksonUtil.newObjectNode(); - resultNode.put("ts", interval.getEndTs() - 1); + resultNode.put("ts", interval.getStartTs()); resultNode.set("values", metricsNode); result.add(resultNode); diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/alarm/AlarmCalculatedFieldState.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/alarm/AlarmCalculatedFieldState.java index 18629bd370..9dd944c2e4 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/alarm/AlarmCalculatedFieldState.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/alarm/AlarmCalculatedFieldState.java @@ -94,8 +94,6 @@ public class AlarmCalculatedFieldState extends BaseCalculatedFieldState { private Alarm currentAlarm; private boolean initialFetchDone; - // TODO: deprecate device profile node, describe the differences and improvements - public AlarmCalculatedFieldState(EntityId entityId) { super(entityId); } diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/geofencing/GeofencingCalculatedFieldState.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/geofencing/GeofencingCalculatedFieldState.java index ea47dafa59..e336672877 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/geofencing/GeofencingCalculatedFieldState.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/geofencing/GeofencingCalculatedFieldState.java @@ -21,8 +21,6 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.geo.Coordinates; @@ -52,11 +50,9 @@ import static org.thingsboard.server.common.data.cf.configuration.geofencing.Ent import static org.thingsboard.server.common.data.cf.configuration.geofencing.GeofencingPresenceStatus.INSIDE; import static org.thingsboard.server.common.data.cf.configuration.geofencing.GeofencingPresenceStatus.OUTSIDE; -@Getter -@Setter @Slf4j @EqualsAndHashCode(callSuper = true) -public class GeofencingCalculatedFieldState extends BaseCalculatedFieldState { +public class GeofencingCalculatedFieldState extends BaseCalculatedFieldState implements ScheduledRefreshSupported { private long lastDynamicArgumentsRefreshTs = DEFAULT_LAST_UPDATE_TS; @@ -147,10 +143,21 @@ public class GeofencingCalculatedFieldState extends BaseCalculatedFieldState { @Override public void reset() { super.reset(); + resetScheduledRefreshTs(); + } + + @Override + public void resetScheduledRefreshTs() { lastDynamicArgumentsRefreshTs = DEFAULT_LAST_UPDATE_TS; } - public void updateLastDynamicArgumentsRefreshTs() { + @Override + public long getLastScheduledRefreshTs() { + return lastDynamicArgumentsRefreshTs; + } + + @Override + public void updateScheduledRefreshTs() { lastDynamicArgumentsRefreshTs = System.currentTimeMillis(); } diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/geofencing/ScheduledRefreshSupported.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/geofencing/ScheduledRefreshSupported.java new file mode 100644 index 0000000000..f43959443a --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/geofencing/ScheduledRefreshSupported.java @@ -0,0 +1,26 @@ +/** + * Copyright © 2016-2025 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.cf.ctx.state.geofencing; + +public interface ScheduledRefreshSupported { + + void resetScheduledRefreshTs(); + + long getLastScheduledRefreshTs(); + + void updateScheduledRefreshTs(); + +} diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/propagation/PropagationArgumentEntry.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/propagation/PropagationArgumentEntry.java index 0450a0599a..8536c0f65f 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/propagation/PropagationArgumentEntry.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/propagation/PropagationArgumentEntry.java @@ -22,6 +22,8 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.service.cf.ctx.state.ArgumentEntry; import org.thingsboard.server.service.cf.ctx.state.ArgumentEntryType; +import java.util.ArrayList; +import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -30,10 +32,11 @@ import java.util.Set; public class PropagationArgumentEntry implements ArgumentEntry { private Set entityIds; - private transient EntityId added; + private transient List added; private transient EntityId removed; - private boolean forceResetPrevious; + private transient boolean forceResetPrevious; + private transient boolean ignoreRemovedEntities; public PropagationArgumentEntry() { this.entityIds = new HashSet<>(); @@ -57,27 +60,44 @@ public class PropagationArgumentEntry implements ArgumentEntry { @Override public boolean updateEntry(ArgumentEntry entry) { - if (!(entry instanceof PropagationArgumentEntry propagationArgumentEntry)) { + if (!(entry instanceof PropagationArgumentEntry updated)) { throw new IllegalArgumentException("Unsupported argument entry type for propagation argument entry: " + entry.getType()); } - if (propagationArgumentEntry.getAdded() != null) { - boolean updated = entityIds.add(propagationArgumentEntry.getAdded()); - if (updated) { - added = propagationArgumentEntry.getAdded(); - } - return updated; + if (updated.getAdded() != null) { + return checkAdded(updated.getAdded()); + } + if (updated.getRemoved() != null) { + return entityIds.remove(updated.getRemoved()); } - if (propagationArgumentEntry.getRemoved() != null) { - return entityIds.remove(propagationArgumentEntry.getRemoved()); + if (updated.isIgnoreRemovedEntities()) { + Set updatedIds = updated.getEntityIds(); + if (updatedIds.isEmpty()) { + entityIds.clear(); + return false; + } + entityIds.retainAll(updatedIds); + return checkAdded(updatedIds); } - if (propagationArgumentEntry.isEmpty()) { + if (updated.isEmpty()) { entityIds.clear(); return true; } - entityIds = propagationArgumentEntry.getEntityIds(); + entityIds = updated.getEntityIds(); return true; } + private boolean checkAdded(Collection updatedIds) { + for (EntityId id : updatedIds) { + if (entityIds.add(id)) { + if (added == null) { + added = new ArrayList<>(); + } + added.add(id); + } + } + return added != null && !added.isEmpty(); + } + @Override public boolean isEmpty() { return entityIds.isEmpty(); diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/propagation/PropagationCalculatedFieldState.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/propagation/PropagationCalculatedFieldState.java index 5a7753c86a..c4533aef37 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/propagation/PropagationCalculatedFieldState.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/propagation/PropagationCalculatedFieldState.java @@ -25,6 +25,8 @@ import org.thingsboard.server.common.data.cf.CalculatedFieldType; import org.thingsboard.server.common.data.cf.configuration.Output; import org.thingsboard.server.common.data.cf.configuration.OutputType; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.util.CollectionsUtil; +import org.thingsboard.server.service.cf.CalculatedFieldProcessingService; import org.thingsboard.server.service.cf.CalculatedFieldResult; import org.thingsboard.server.service.cf.PropagationCalculatedFieldResult; import org.thingsboard.server.service.cf.TelemetryCalculatedFieldResult; @@ -41,6 +43,8 @@ import static org.thingsboard.server.common.data.cf.configuration.PropagationCal public class PropagationCalculatedFieldState extends ScriptCalculatedFieldState { + private CalculatedFieldProcessingService cfProcessingService; + public PropagationCalculatedFieldState(EntityId entityId) { super(entityId); } @@ -49,14 +53,30 @@ public class PropagationCalculatedFieldState extends ScriptCalculatedFieldState public void setCtx(CalculatedFieldCtx ctx, TbActorRef actorCtx) { this.ctx = ctx; this.actorCtx = actorCtx; + this.cfProcessingService = ctx.getCfProcessingService(); this.requiredArguments = new ArrayList<>(ctx.getArgNames()); requiredArguments.add(PROPAGATION_CONFIG_ARGUMENT); - this.readinessStatus = checkReadiness(requiredArguments, arguments); + this.readinessStatus = checkReadiness(); if (ctx.isApplyExpressionForResolvedArguments()) { this.tbelExpression = ctx.getTbelExpressions().get(ctx.getExpression()); } } + @Override + public void init(boolean restored) { + super.init(restored); + if (restored) { + cfProcessingService.fetchPropagationArgumentFromDb(ctx, entityId).ifPresent(fromDb -> { + fromDb.setIgnoreRemovedEntities(true); + var updatedArgs = update(Map.of(PROPAGATION_CONFIG_ARGUMENT, fromDb), ctx); + if (updatedArgs.isEmpty()) { + return; + } + ctx.scheduleReevaluation(0L, actorCtx); + }); + } + } + @Override public CalculatedFieldType getType() { return CalculatedFieldType.PROPAGATION; @@ -68,9 +88,10 @@ public class PropagationCalculatedFieldState extends ScriptCalculatedFieldState if (!(argumentEntry instanceof PropagationArgumentEntry propagationArgumentEntry)) { return Futures.immediateFuture(PropagationCalculatedFieldResult.builder().build()); } + boolean newEntityAdded = propagationArgumentEntry.getAdded() != null; List entityIds; - if (propagationArgumentEntry.getAdded() != null) { - entityIds = List.of(propagationArgumentEntry.getAdded()); + if (newEntityAdded) { + entityIds = propagationArgumentEntry.getAdded(); propagationArgumentEntry.setAdded(null); } else { if (propagationArgumentEntry.getEntityIds().isEmpty()) { @@ -86,13 +107,43 @@ public class PropagationCalculatedFieldState extends ScriptCalculatedFieldState .build(), MoreExecutors.directExecutor()); } + if (newEntityAdded || CollectionsUtil.isEmpty(updatedArgs)) { + updatedArgs = arguments; + } return Futures.immediateFuture(PropagationCalculatedFieldResult.builder() .entityIds(entityIds) - .result(toTelemetryResult(ctx)) + .result(toTelemetryResult(ctx, updatedArgs)) .build()); } - private TelemetryCalculatedFieldResult toTelemetryResult(CalculatedFieldCtx ctx) { + @Override + protected ReadinessStatus checkReadiness() { + if (ctx.isApplyExpressionForResolvedArguments() || arguments == null) { + return super.checkReadiness(); + } + boolean propagationNotEmpty = false; + boolean hasOtherNonEmpty = false; + List emptyArguments = null; + for (String requiredArgumentKey : requiredArguments) { + ArgumentEntry argumentEntry = arguments.get(requiredArgumentKey); + if (argumentEntry == null || argumentEntry.isEmpty()) { + if (emptyArguments == null) { + emptyArguments = new ArrayList<>(); + } + emptyArguments.add(requiredArgumentKey); + } else if (PROPAGATION_CONFIG_ARGUMENT.equals(requiredArgumentKey)) { + propagationNotEmpty = true; + } else { + hasOtherNonEmpty = true; + } + } + if (propagationNotEmpty && hasOtherNonEmpty) { + return ReadinessStatus.READY; + } + return ReadinessStatus.from(emptyArguments); + } + + private TelemetryCalculatedFieldResult toTelemetryResult(CalculatedFieldCtx ctx, Map updatedArgs) { Output output = ctx.getOutput(); TelemetryCalculatedFieldResult.TelemetryCalculatedFieldResultBuilder telemetryCfBuilder = TelemetryCalculatedFieldResult.builder() @@ -100,12 +151,14 @@ public class PropagationCalculatedFieldState extends ScriptCalculatedFieldState .type(output.getType()) .scope(output.getScope()); ObjectNode valuesNode = JacksonUtil.newObjectNode(); - arguments.forEach((outputKey, argumentEntry) -> { + updatedArgs.forEach((outputKey, argumentEntry) -> { if (argumentEntry instanceof PropagationArgumentEntry) { return; } if (argumentEntry instanceof SingleValueArgumentEntry singleArgumentEntry) { - JacksonUtil.addKvEntry(valuesNode, singleArgumentEntry.getKvEntryValue(), outputKey); + if (!singleArgumentEntry.isEmpty()) { + JacksonUtil.addKvEntry(valuesNode, singleArgumentEntry.getKvEntryValue(), outputKey); + } return; } throw new IllegalArgumentException("Unsupported argument type: " + argumentEntry.getType() + " detected for argument: " + outputKey + ". " + diff --git a/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java b/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java index ec4a43310b..d523a9176c 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java @@ -206,7 +206,6 @@ public class EdgeContextComponent { @Autowired private Optional statsCounterService; - // processors @Autowired private AlarmProcessor alarmProcessor; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/EdgeMsgConstructorUtils.java b/application/src/main/java/org/thingsboard/server/service/edge/EdgeMsgConstructorUtils.java index 505b03a1cd..6b50b8ae15 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/EdgeMsgConstructorUtils.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/EdgeMsgConstructorUtils.java @@ -21,6 +21,7 @@ import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import com.google.gson.JsonPrimitive; import com.google.gson.reflect.TypeToken; import lombok.extern.slf4j.Slf4j; @@ -52,6 +53,7 @@ import org.thingsboard.server.common.data.asset.AssetProfile; import org.thingsboard.server.common.data.cf.CalculatedField; import org.thingsboard.server.common.data.domain.DomainInfo; import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.id.AiModelId; import org.thingsboard.server.common.data.id.AssetId; @@ -76,6 +78,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.id.WidgetsBundleId; +import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.notification.rule.NotificationRule; import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.notification.template.NotificationTemplate; @@ -88,6 +91,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.common.data.widget.WidgetTypeDetails; import org.thingsboard.server.common.data.widget.WidgetsBundle; +import org.thingsboard.server.common.transport.util.JsonUtils; import org.thingsboard.server.gen.edge.v1.AiModelUpdateMsg; import org.thingsboard.server.gen.edge.v1.AlarmCommentUpdateMsg; import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg; @@ -127,11 +131,16 @@ import org.thingsboard.server.gen.edge.v1.WidgetTypeUpdateMsg; import org.thingsboard.server.gen.edge.v1.WidgetsBundleUpdateMsg; import org.thingsboard.server.gen.transport.TransportProtos; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; +import java.util.stream.Collectors; @Slf4j public class EdgeMsgConstructorUtils { @@ -670,4 +679,128 @@ public class EdgeMsgConstructorUtils { .setIdLSB(aiModelId.getId().getLeastSignificantBits()).build(); } + public static List mergeAndFilterDownlinkDuplicates(List edgeEvents) { + try { + edgeEvents = removeDownlinkDuplicates(edgeEvents); + + List attrUpdateMsgs = new ArrayList<>(); + for (EdgeEvent edgeEvent : edgeEvents) { + if (EdgeEventActionType.ATTRIBUTES_UPDATED.equals(edgeEvent.getAction())) { + attrUpdateMsgs.add(new AttrUpdateMsg(edgeEvent.getEntityId(), edgeEvent.getBody())); + } + } + Map> latestTsByEntityAndKey = computeLatestTsByEntityAndKey(attrUpdateMsgs); + + List result = new ArrayList<>(); + for (EdgeEvent edgeEvent : edgeEvents) { + if (!EdgeEventActionType.ATTRIBUTES_UPDATED.equals(edgeEvent.getAction())) { + result.add(edgeEvent); + continue; + } + + Map latestByKey = latestTsByEntityAndKey.get(edgeEvent.getEntityId()); + JsonNode filteredBody = filterAttributesBody(edgeEvent.getBody(), latestByKey); + if (filteredBody == null) { + continue; + } + + result.add(createFilteredEdgeEvent(edgeEvent, filteredBody)); + } + + result.sort(Comparator.comparingLong(EdgeEvent::getSeqId)); + return result; + } catch (Exception e) { + log.warn("Can't merge downlink duplicates, edgeEvents [{}]", edgeEvents, e); + return edgeEvents; + } + } + + private static AttrsTs extractAttributes(JsonNode body) { + if (body == null) { + return new AttrsTs(0L, List.of()); + } + String bodyStr = JacksonUtil.toString(body); + var jsonObject = JsonParser.parseString(bodyStr).getAsJsonObject(); + long ts = jsonObject.get("ts").getAsLong(); + var kv = jsonObject.getAsJsonObject("kv"); + List attrs = JsonConverter.convertToAttributes( + JsonUtils.getJsonObject( + JsonConverter.convertToAttributesProto(kv).getKvList() + ), ts); + return new AttrsTs(ts, attrs); + } + + private static JsonNode filterAttributesBody(JsonNode body, Map latestByKey) { + if (body == null || latestByKey == null || latestByKey.isEmpty()) { + return null; + } + String bodyStr = JacksonUtil.toString(body); + JsonObject jsonObject = JsonParser.parseString(bodyStr).getAsJsonObject(); + long ts = jsonObject.get("ts").getAsLong(); + JsonObject kv = jsonObject.getAsJsonObject("kv"); + for (Iterator> it = kv.entrySet().iterator(); it.hasNext(); ) { + Map.Entry e = it.next(); + Long latestTs = latestByKey.get(e.getKey()); + if (latestTs == null || !latestTs.equals(ts)) { + it.remove(); + } + } + if (kv.isEmpty()) { + return null; + } + return JacksonUtil.toJsonNode(jsonObject.toString()); + } + + private static Map> computeLatestTsByEntityAndKey(List attrUpdateMsgs) { + Map> latestTsByEntityAndKey = new HashMap<>(); + for (AttrUpdateMsg attrUpdateMsg : attrUpdateMsgs) { + UUID entityId = attrUpdateMsg.entityId(); + AttrsTs attrsTs = extractAttributes(attrUpdateMsg.body()); + Map map = latestTsByEntityAndKey.computeIfAbsent(entityId, id -> new HashMap<>()); + long ts = attrsTs.ts(); + for (AttributeKvEntry attr : attrsTs.attrs()) { + map.merge(attr.getKey(), ts, Math::max); + } + } + return latestTsByEntityAndKey; + } + + private static EdgeEvent createFilteredEdgeEvent(EdgeEvent edgeEvent, JsonNode filteredBody) { + EdgeEvent filtered = new EdgeEvent(); + filtered.setSeqId(edgeEvent.getSeqId()); + filtered.setTenantId(edgeEvent.getTenantId()); + filtered.setEdgeId(edgeEvent.getEdgeId()); + filtered.setAction(edgeEvent.getAction()); + filtered.setEntityId(edgeEvent.getEntityId()); + filtered.setUid(edgeEvent.getUid()); + filtered.setType(edgeEvent.getType()); + filtered.setBody(filteredBody); + return filtered; + } + + private static List removeDownlinkDuplicates(List edgeEvents) { + Set seen = new HashSet<>(); + return edgeEvents.stream() + .filter(e -> seen.add(new EventKey( + e.getTenantId(), + e.getAction(), + e.getEntityId(), + e.getType().name(), + (e.getBody() != null ? e.getBody().toString() : "null")))) + .collect(Collectors.toList()); + } + + private record EventKey(TenantId tenantId, + EdgeEventActionType action, + UUID entityId, + String type, + String body) { + } + + private record AttrsTs(long ts, List attrs) { + } + + private record AttrUpdateMsg(UUID entityId, JsonNode body) { + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java index 14c1b5e30a..e2790b5c86 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java @@ -306,7 +306,8 @@ public abstract class EdgeGrpcSession implements Closeable { if (isConnected() && !pageData.getData().isEmpty()) { if (fetcher instanceof GeneralEdgeEventFetcher) { long queueSize = pageData.getTotalElements() - ((long) pageLink.getPageSize() * pageLink.getPage()); - ctx.getStatsCounterService().ifPresent(statsCounterService -> statsCounterService.setDownlinkMsgsLag(edge.getTenantId(), edge.getId(), queueSize)); + ctx.getStatsCounterService().ifPresent(statsCounterService -> + statsCounterService.recordEvent(EdgeStatsKey.DOWNLINK_MSGS_LAG, tenantId, edge.getId(), queueSize)); } log.trace("[{}][{}][{}] event(s) are going to be processed.", tenantId, edge.getId(), pageData.getData().size()); List downlinkMsgsPack = convertToDownlinkMsgsPack(pageData.getData()); @@ -504,7 +505,8 @@ public abstract class EdgeGrpcSession implements Closeable { ctx.getRuleProcessor().process(EdgeCommunicationFailureTrigger.builder().tenantId(tenantId).edgeId(edge.getId()) .customerId(edge.getCustomerId()).edgeName(edge.getName()).failureMsg(failureMsg) .error("Failed to deliver messages after " + MAX_DOWNLINK_ATTEMPTS + " attempts").build()); - ctx.getStatsCounterService().ifPresent(statsCounterService -> statsCounterService.recordEvent(EdgeStatsKey.DOWNLINK_MSGS_PERMANENTLY_FAILED, edge.getTenantId(), edge.getId(), copy.size())); + ctx.getStatsCounterService().ifPresent(statsCounterService -> + statsCounterService.recordEvent(EdgeStatsKey.DOWNLINK_MSGS_PERMANENTLY_FAILED, edge.getTenantId(), edge.getId(), copy.size())); stopCurrentSendDownlinkMsgsTask(false); } } else { @@ -544,7 +546,8 @@ public abstract class EdgeGrpcSession implements Closeable { try { if (msg.getSuccess()) { sessionState.getPendingMsgsMap().remove(msg.getDownlinkMsgId()); - ctx.getStatsCounterService().ifPresent(statsCounterService -> statsCounterService.recordEvent(EdgeStatsKey.DOWNLINK_MSGS_PUSHED, edge.getTenantId(), edge.getId(), 1)); + ctx.getStatsCounterService().ifPresent(statsCounterService -> + statsCounterService.recordEvent(EdgeStatsKey.DOWNLINK_MSGS_PUSHED, edge.getTenantId(), edge.getId(), 1)); log.debug("[{}][{}][{}] Msg has been processed successfully! Msg Id: [{}], Msg: {}", tenantId, edge.getId(), sessionId, msg.getDownlinkMsgId(), msg); } else { log.debug("[{}][{}][{}] Msg processing failed! Msg Id: [{}], Error msg: {}", tenantId, edge.getId(), sessionId, msg.getDownlinkMsgId(), msg.getErrorMsg()); @@ -651,7 +654,8 @@ public abstract class EdgeGrpcSession implements Closeable { protected List convertToDownlinkMsgsPack(List edgeEvents) { List result = new ArrayList<>(); - for (EdgeEvent edgeEvent : edgeEvents) { + List filtered = EdgeMsgConstructorUtils.mergeAndFilterDownlinkDuplicates(edgeEvents); + for (EdgeEvent edgeEvent : filtered) { log.trace("[{}][{}] converting edge event to downlink msg [{}]", tenantId, edge.getId(), edgeEvent); DownlinkMsg downlinkMsg = null; try { @@ -813,7 +817,8 @@ public abstract class EdgeGrpcSession implements Closeable { } } highPriorityQueue.add(edgeEvent); - ctx.getStatsCounterService().ifPresent(statsCounterService -> statsCounterService.recordEvent(EdgeStatsKey.DOWNLINK_MSGS_ADDED, edge.getTenantId(), edgeEvent.getEdgeId(), 1)); + ctx.getStatsCounterService().ifPresent(statsCounterService -> + statsCounterService.recordEvent(EdgeStatsKey.DOWNLINK_MSGS_ADDED, edge.getTenantId(), edgeEvent.getEdgeId(), 1)); } protected ListenableFuture> processUplinkMsg(UplinkMsg uplinkMsg) { diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java index a8960abf0b..31f80b6c96 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java @@ -92,6 +92,7 @@ public class EdgeSyncCursor { fetchers.add(new NotificationTargetEdgeEventFetcher(ctx.getNotificationTargetService())); fetchers.add(new NotificationRuleEdgeEventFetcher(ctx.getNotificationRuleService())); fetchers.add(new OtaPackagesEdgeEventFetcher(ctx.getOtaPackageService())); + // sync device profiles twice to update software and hardware fields fetchers.add(new DeviceProfilesEdgeEventFetcher(ctx.getDeviceProfileService())); fetchers.add(new TenantResourcesEdgeEventFetcher(ctx.getResourceService())); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/KafkaEdgeEventService.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/KafkaEdgeEventService.java index bc00ef4481..b4f7a7574f 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/KafkaEdgeEventService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/KafkaEdgeEventService.java @@ -52,7 +52,8 @@ public class KafkaEdgeEventService extends BaseEdgeEventService { TopicPartitionInfo tpi = topicService.getEdgeEventNotificationsTopic(edgeEvent.getTenantId(), edgeEvent.getEdgeId()); ToEdgeEventNotificationMsg msg = ToEdgeEventNotificationMsg.newBuilder().setEdgeEventMsg(ProtoUtils.toProto(edgeEvent)).build(); producerProvider.getTbEdgeEventsMsgProducer().send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), msg), null); - statsCounterService.ifPresent(statsCounterService -> statsCounterService.recordEvent(EdgeStatsKey.DOWNLINK_MSGS_ADDED, edgeEvent.getTenantId(), edgeEvent.getEdgeId(), 1)); + statsCounterService.ifPresent(statsCounterService -> + statsCounterService.recordEvent(EdgeStatsKey.DOWNLINK_MSGS_ADDED, edgeEvent.getTenantId(), edgeEvent.getEdgeId(), 1)); return Futures.immediateFuture(null); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/ai/AiModelEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/ai/AiModelEdgeProcessor.java index cf1c9d9abd..1b70b8bd32 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/ai/AiModelEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/ai/AiModelEdgeProcessor.java @@ -29,7 +29,7 @@ import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.id.AiModelId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.msg.TbMsgType; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.gen.edge.v1.AiModelUpdateMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.EdgeVersion; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessor.java index a4b35ab541..88e3113039 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessor.java @@ -33,7 +33,7 @@ import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.msg.TbMsgMetaData; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.EdgeVersion; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/AssetProfileEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/AssetProfileEdgeProcessor.java index e05b6f41c3..5c09f2507b 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/AssetProfileEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/AssetProfileEdgeProcessor.java @@ -33,7 +33,7 @@ import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.msg.TbMsgMetaData; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.EdgeVersion; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/cf/CalculatedFieldEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/cf/CalculatedFieldEdgeProcessor.java index cab4b5ecc1..5e7099c9d8 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/cf/CalculatedFieldEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/cf/CalculatedFieldEdgeProcessor.java @@ -36,7 +36,7 @@ import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.msg.TbMsgMetaData; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.gen.edge.v1.CalculatedFieldUpdateMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.EdgeVersion; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardEdgeProcessor.java index b38a9d618e..23c7e3cbcb 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardEdgeProcessor.java @@ -29,7 +29,7 @@ import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.msg.TbMsgType; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.gen.edge.v1.DashboardUpdateMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.EdgeVersion; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java index 925e903ab2..2f655d961b 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java @@ -44,7 +44,7 @@ import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse; import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponseActorMsg; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.gen.edge.v1.DeviceCredentialsUpdateMsg; import org.thingsboard.server.gen.edge.v1.DeviceRpcCallMsg; import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/DeviceProfileEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/DeviceProfileEdgeProcessor.java index 3355bc819f..540fd4b448 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/DeviceProfileEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/DeviceProfileEdgeProcessor.java @@ -33,7 +33,7 @@ import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.msg.TbMsgMetaData; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.EdgeVersion; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewEdgeProcessor.java index 3b6aafea74..46923c3434 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewEdgeProcessor.java @@ -32,7 +32,7 @@ import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.msg.TbMsgMetaData; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.EntityViewUpdateMsg; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessor.java index de31bf6a9c..28b6c49bc1 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessor.java @@ -27,7 +27,7 @@ import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.id.TbResourceId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.ResourceUpdateMsg; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainEdgeProcessor.java index b949660e70..5b875b1b0f 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainEdgeProcessor.java @@ -31,7 +31,7 @@ import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.msg.TbMsgMetaData; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/user/UserEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/user/UserEdgeProcessor.java index 3b2d76356b..4a02aeda1d 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/user/UserEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/user/UserEdgeProcessor.java @@ -33,7 +33,7 @@ import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.common.msg.TbMsgMetaData; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/stats/EdgeStatsService.java b/application/src/main/java/org/thingsboard/server/service/edge/stats/EdgeStatsService.java index 48b2a47cfb..7f55d2817c 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/stats/EdgeStatsService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/stats/EdgeStatsService.java @@ -54,7 +54,7 @@ import static org.thingsboard.server.dao.edge.stats.EdgeStatsKey.DOWNLINK_MSGS_P import static org.thingsboard.server.dao.edge.stats.EdgeStatsKey.DOWNLINK_MSGS_TMP_FAILED; @TbCoreComponent -@ConditionalOnProperty(prefix = "edges.stats", name = "enabled", havingValue = "true", matchIfMissing = false) +@ConditionalOnProperty(prefix = "edges.stats", name = "enabled", havingValue = "true") @RequiredArgsConstructor @Service @Slf4j @@ -70,7 +70,6 @@ public class EdgeStatsService { @Value("${edges.stats.report-interval-millis:600000}") private long reportIntervalMillis; - @Scheduled( fixedDelayString = "${edges.stats.report-interval-millis:600000}", initialDelayString = "${edges.stats.report-interval-millis:600000}" @@ -80,13 +79,13 @@ public class EdgeStatsService { long now = System.currentTimeMillis(); long ts = now - (now % reportIntervalMillis); - Map countersByEdge = statsCounterService.getCounterByEdge(); - Map lagByEdgeId = kafkaAdmin.isPresent() ? getEdgeLagByEdgeId(countersByEdge) : Collections.emptyMap(); - Map countersByEdgeSnapshot = new HashMap<>(statsCounterService.getCounterByEdge()); + Map countersByEdgeSnapshot = new HashMap<>(statsCounterService.getMsgCountersByEdge()); + boolean isKafkaStats = kafkaAdmin.isPresent(); + Map lagByEdgeId = isKafkaStats ? getLagByEdgeId(countersByEdgeSnapshot) : Collections.emptyMap(); countersByEdgeSnapshot.forEach((edgeId, counters) -> { TenantId tenantId = counters.getTenantId(); - if (kafkaAdmin.isPresent()) { + if (isKafkaStats) { counters.getMsgsLag().set(lagByEdgeId.getOrDefault(edgeId, 0L)); } List statsEntries = List.of( @@ -102,7 +101,7 @@ public class EdgeStatsService { }); } - private Map getEdgeLagByEdgeId(Map countersByEdge) { + private Map getLagByEdgeId(Map countersByEdge) { Map edgeToTopicMap = countersByEdge.entrySet().stream() .collect(Collectors.toMap( Map.Entry::getKey, diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index a55832c823..16e164877a 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -103,7 +103,7 @@ import org.thingsboard.server.dao.device.DeviceConnectivityConfiguration; import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceService; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.mobile.MobileAppDao; import org.thingsboard.server.dao.notification.NotificationSettingsService; import org.thingsboard.server.dao.notification.NotificationTargetService; diff --git a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java index f9b5c282a8..a344c9ea08 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java +++ b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java @@ -39,7 +39,7 @@ import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.widget.WidgetTypeDetails; import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.dao.dashboard.DashboardService; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService; import org.thingsboard.server.dao.resource.ImageService; import org.thingsboard.server.dao.resource.ResourceService; diff --git a/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java b/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java index f4183dc904..bf9cece710 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java +++ b/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java @@ -69,7 +69,7 @@ import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.entityview.EntityViewService; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.resource.ResourceService; diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/DefaultJwtSettingsValidator.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/DefaultJwtSettingsValidator.java index 63f05f6255..0f2c4f346f 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/DefaultJwtSettingsValidator.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/DefaultJwtSettingsValidator.java @@ -21,7 +21,7 @@ import org.apache.commons.lang3.StringUtils; import org.bouncycastle.util.Arrays; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.security.model.JwtSettings; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import java.util.Base64; import java.util.Optional; diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/config/DefaultTwoFaConfigManager.java b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/config/DefaultTwoFaConfigManager.java index 3f7d711378..a742b8cd2a 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/config/DefaultTwoFaConfigManager.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/config/DefaultTwoFaConfigManager.java @@ -29,11 +29,11 @@ import org.thingsboard.server.common.data.security.model.mfa.account.AccountTwoF import org.thingsboard.server.common.data.security.model.mfa.account.TwoFaAccountConfig; import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderConfig; import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderType; -import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.ConstraintValidator; import org.thingsboard.server.dao.settings.AdminSettingsDao; import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.dao.user.UserAuthSettingsDao; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.service.security.auth.mfa.TwoFactorAuthService; import java.util.Comparator; diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAuthenticationProvider.java b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAuthenticationProvider.java index e43a89f815..d137b8df40 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAuthenticationProvider.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAuthenticationProvider.java @@ -33,7 +33,7 @@ import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.common.data.security.model.SecuritySettings; import org.thingsboard.server.common.data.security.model.UserPasswordPolicy; import org.thingsboard.server.dao.customer.CustomerService; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.settings.SecuritySettingsService; import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.queue.util.TbCoreComponent; diff --git a/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java b/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java index 69536f0953..7d4177e5b5 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java +++ b/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java @@ -50,7 +50,7 @@ import org.thingsboard.server.common.data.security.model.SecuritySettings; import org.thingsboard.server.common.data.security.model.UserPasswordPolicy; import org.thingsboard.server.common.data.security.model.mfa.PlatformTwoFaSettings; import org.thingsboard.server.dao.audit.AuditLogService; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.dao.settings.SecuritySettingsService; import org.thingsboard.server.dao.user.UserService; diff --git a/application/src/main/java/org/thingsboard/server/service/security/system/SystemSecurityService.java b/application/src/main/java/org/thingsboard/server/service/security/system/SystemSecurityService.java index 1a742c6b30..753cc9fd72 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/system/SystemSecurityService.java +++ b/application/src/main/java/org/thingsboard/server/service/security/system/SystemSecurityService.java @@ -24,7 +24,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.common.data.security.model.UserPasswordPolicy; import org.thingsboard.server.common.data.security.model.mfa.PlatformTwoFaSettings; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.service.security.model.SecurityUser; public interface SystemSecurityService { diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java index 506b48bf8c..cf0d58e5ba 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java @@ -31,7 +31,7 @@ import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.sync.ie.EntityExportData; import org.thingsboard.server.common.data.sync.ie.EntityImportResult; import org.thingsboard.server.common.data.util.ThrowingRunnable; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.TbLogEntityActionService; diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index 096c1a814a..58a5b951d5 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -22,6 +22,7 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; import com.google.protobuf.ByteString; +import org.thingsboard.server.exception.EntitiesLimitExceededException; import jakarta.annotation.PostConstruct; import jakarta.annotation.PreDestroy; import lombok.RequiredArgsConstructor; @@ -76,7 +77,6 @@ import org.thingsboard.server.dao.device.provision.ProvisionFailedException; import org.thingsboard.server.dao.device.provision.ProvisionRequest; import org.thingsboard.server.dao.device.provision.ProvisionResponse; import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus; -import org.thingsboard.server.dao.exception.EntitiesLimitExceededException; import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.relation.RelationService; diff --git a/application/src/main/java/org/thingsboard/server/utils/CalculatedFieldUtils.java b/application/src/main/java/org/thingsboard/server/utils/CalculatedFieldUtils.java index 7046af3d9c..9b284e983f 100644 --- a/application/src/main/java/org/thingsboard/server/utils/CalculatedFieldUtils.java +++ b/application/src/main/java/org/thingsboard/server/utils/CalculatedFieldUtils.java @@ -33,6 +33,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ArgumentIntervalProt import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldEntityCtxIdProto; import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldIdProto; import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldStateProto; +import org.thingsboard.server.gen.transport.TransportProtos.EntityIdProto; import org.thingsboard.server.gen.transport.TransportProtos.GeofencingArgumentProto; import org.thingsboard.server.gen.transport.TransportProtos.GeofencingZoneProto; import org.thingsboard.server.gen.transport.TransportProtos.SingleValueArgumentProto; @@ -61,6 +62,7 @@ import org.thingsboard.server.service.cf.ctx.state.propagation.PropagationArgume import org.thingsboard.server.service.cf.ctx.state.propagation.PropagationCalculatedFieldState; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.TreeMap; @@ -108,10 +110,15 @@ public class CalculatedFieldUtils { case SINGLE_VALUE -> builder.addSingleValueArguments(toSingleValueArgumentProto(argName, (SingleValueArgumentEntry) argEntry)); case TS_ROLLING -> builder.addRollingValueArguments(toRollingArgumentProto(argName, (TsRollingArgumentEntry) argEntry)); case GEOFENCING -> builder.addGeofencingArguments(toGeofencingArgumentProto(argName, (GeofencingArgumentEntry) argEntry)); + case PROPAGATION -> builder.addAllPropagationEntityIds(toPropagationEntityIdsProto((PropagationArgumentEntry) argEntry)); case RELATED_ENTITIES -> { RelatedEntitiesArgumentEntry relatedEntitiesArgumentEntry = (RelatedEntitiesArgumentEntry) argEntry; - relatedEntitiesArgumentEntry.getEntityInputs() - .forEach((entityId, entry) -> builder.addSingleValueArguments(toSingleValueArgumentProto(argName, (SingleValueArgumentEntry) entry))); + Map entityInputs = relatedEntitiesArgumentEntry.getEntityInputs(); + if (entityInputs.isEmpty()) { + builder.addSingleValueArguments(SingleValueArgumentProto.newBuilder().setArgName(argName).build()); + } else { + entityInputs.forEach((entityId, entry) -> builder.addSingleValueArguments(toSingleValueArgumentProto(argName, (SingleValueArgumentEntry) entry))); + } } case ENTITY_AGGREGATION -> { EntityAggregationArgumentEntry entityAggregationArgumentEntry = (EntityAggregationArgumentEntry) argEntry; @@ -136,6 +143,10 @@ public class CalculatedFieldUtils { return builder.build(); } + private static List toPropagationEntityIdsProto(PropagationArgumentEntry argEntry) { + return argEntry.getEntityIds().stream().map(ProtoUtils::toProto).collect(Collectors.toList()); + } + private static AlarmRuleStateProto toAlarmRuleStateProto(AlarmRuleState ruleState) { return AlarmRuleStateProto.newBuilder() .setSeverity(Optional.ofNullable(ruleState.getSeverity()).map(Enum::name).orElse("")) @@ -234,7 +245,10 @@ public class CalculatedFieldUtils { Map> arguments = new HashMap<>(); proto.getSingleValueArgumentsList().forEach(argProto -> { SingleValueArgumentEntry entry = fromSingleValueArgumentProto(argProto); - arguments.computeIfAbsent(argProto.getArgName(), name -> new HashMap<>()).put(entry.getEntityId(), entry); + Map entityInputs = arguments.computeIfAbsent(argProto.getArgName(), name -> new HashMap<>()); + if (entry.getEntityId() != null) { + entityInputs.put(entry.getEntityId(), entry); + } }); arguments.forEach((argName, entityInputs) -> { relatedEntitiesAggState.getArguments().put(argName, new RelatedEntitiesArgumentEntry(entityInputs, false)); @@ -268,7 +282,10 @@ public class CalculatedFieldUtils { state.getArguments().put(argProto.getKey(), fromRollingArgumentProto(argProto))); case GEOFENCING -> proto.getGeofencingArgumentsList().forEach(argProto -> state.getArguments().put(argProto.getArgName(), fromGeofencingArgumentProto(argProto))); - case PROPAGATION -> state.getArguments().put(PROPAGATION_CONFIG_ARGUMENT, new PropagationArgumentEntry()); + case PROPAGATION -> { + List propagationEntityIds = proto.getPropagationEntityIdsList().stream().map(ProtoUtils::fromProto).toList(); + state.getArguments().put(PROPAGATION_CONFIG_ARGUMENT, new PropagationArgumentEntry(propagationEntityIds)); + } case ALARM -> { AlarmCalculatedFieldState alarmState = (AlarmCalculatedFieldState) state; AlarmStateProto alarmStateProto = proto.getAlarmState(); diff --git a/application/src/main/java/org/thingsboard/server/utils/LwM2mObjectModelUtils.java b/application/src/main/java/org/thingsboard/server/utils/LwM2mObjectModelUtils.java index 172a3e8e2d..c72620bb8f 100644 --- a/application/src/main/java/org/thingsboard/server/utils/LwM2mObjectModelUtils.java +++ b/application/src/main/java/org/thingsboard/server/utils/LwM2mObjectModelUtils.java @@ -26,7 +26,7 @@ import org.thingsboard.server.common.data.lwm2m.LwM2mInstance; import org.thingsboard.server.common.data.lwm2m.LwM2mObject; import org.thingsboard.server.common.data.lwm2m.LwM2mResourceObserve; import org.thingsboard.server.common.data.util.TbDDFFileParser; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import java.io.ByteArrayInputStream; import java.io.IOException; diff --git a/application/src/test/java/org/thingsboard/server/cf/CalculatedFieldIntegrationTest.java b/application/src/test/java/org/thingsboard/server/cf/CalculatedFieldIntegrationTest.java index f0008d6cfb..a194680ca1 100644 --- a/application/src/test/java/org/thingsboard/server/cf/CalculatedFieldIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/cf/CalculatedFieldIntegrationTest.java @@ -1089,7 +1089,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes // Telemetry on device doPost("/api/plugins/telemetry/DEVICE/" + device.getUuidId() + "/timeseries/unusedScope", - JacksonUtil.toJsonNode("{\"temperature\":12.5}")).andExpect(status().isOk()); + JacksonUtil.toJsonNode("{\"temperature\":12.5, \"humidity\":85}")).andExpect(status().isOk()); // --- Build CF: PROPAGATION with expression --- CalculatedField cf = new CalculatedField(); @@ -1102,11 +1102,14 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes cfg.setRelation(new RelationPathLevel(EntitySearchDirection.TO, EntityRelation.CONTAINS_TYPE)); cfg.setApplyExpressionToResolvedArguments(true); - Argument arg = new Argument(); - arg.setRefEntityKey(new ReferencedEntityKey("temperature", ArgumentType.TS_LATEST, null)); - cfg.setArguments(Map.of("t", arg)); + Argument arg1 = new Argument(); + arg1.setRefEntityKey(new ReferencedEntityKey("temperature", ArgumentType.TS_LATEST, null)); - cfg.setExpression("{\"testResult\": t * 2}"); + Argument arg2 = new Argument(); + arg2.setRefEntityKey(new ReferencedEntityKey("humidity", ArgumentType.TS_LATEST, null)); + + cfg.setArguments(Map.of("t", arg1, "h", arg2)); + cfg.setExpression("return { testResult: (t + h) / 2};"); AttributesOutput output = new AttributesOutput(); output.setScope(AttributeScope.SERVER_SCOPE); @@ -1125,8 +1128,8 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes ArrayNode attrs2 = getServerAttributes(asset2.getId(), "testResult"); assertThat(attrs1).isNotNull(); assertThat(attrs2).isNotNull(); - assertThat(attrs1.get(0).get("value").asDouble()).isEqualTo(25.0); - assertThat(attrs2.get(0).get("value").asDouble()).isEqualTo(25.0); + assertThat(attrs1.get(0).get("value").asDouble()).isEqualTo(48.75); + assertThat(attrs2.get(0).get("value").asDouble()).isEqualTo(48.75); }); String deleteUrl = String.format("/api/v2/relation?fromId=%s&fromType=%s&relationType=%s&toId=%s&toType=%s", @@ -1148,7 +1151,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes ArrayNode attrs2 = getServerAttributes(asset2.getId(), "testResult"); assertThat(attrs1).isNullOrEmpty(); assertThat(attrs2).isNotNull(); - assertThat(attrs2.get(0).get("value").asDouble()).isEqualTo(50); + assertThat(attrs2.get(0).get("value").asDouble()).isEqualTo(55); }); } @@ -1167,7 +1170,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes // Telemetry on device long ts = System.currentTimeMillis() - 300000L; - postTelemetry(device.getId(), String.format("{\"ts\": %s, \"values\": {\"temperature\":12.5}}", ts)); + postTelemetry(device.getId(), String.format("{\"ts\": %s, \"values\": {\"temperature\":12.5, \"humidity\":85}}", ts)); // --- Build CF: PROPAGATION without expression --- CalculatedField cf = new CalculatedField(); @@ -1180,9 +1183,12 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes cfg.setRelation(new RelationPathLevel(EntitySearchDirection.TO, EntityRelation.CONTAINS_TYPE)); cfg.setApplyExpressionToResolvedArguments(false); // arguments-only mode - Argument arg = new Argument(); - arg.setRefEntityKey(new ReferencedEntityKey("temperature", ArgumentType.TS_LATEST, null)); - cfg.setArguments(Map.of("temperatureComputed", arg)); + Argument arg1 = new Argument(); + arg1.setRefEntityKey(new ReferencedEntityKey("temperature", ArgumentType.TS_LATEST, null)); + Argument arg2 = new Argument(); + arg2.setRefEntityKey(new ReferencedEntityKey("humidity", ArgumentType.TS_LATEST, null)); + + cfg.setArguments(Map.of("temperatureComputed", arg1, "humidityComputed", arg2)); TimeSeriesOutput output = new TimeSeriesOutput(); output.setStrategy(new TimeSeriesImmediateOutputStrategy(0, true, true, true, true)); @@ -1197,14 +1203,18 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes .atMost(TIMEOUT, TimeUnit.SECONDS) .pollInterval(POLL_INTERVAL, TimeUnit.SECONDS) .untilAsserted(() -> { - ObjectNode telemetry1 = getLatestTelemetry(asset1.getId(), "temperatureComputed"); - ObjectNode telemetry2 = getLatestTelemetry(asset2.getId(), "temperatureComputed"); + ObjectNode telemetry1 = getLatestTelemetry(asset1.getId(), "temperatureComputed,humidityComputed"); + ObjectNode telemetry2 = getLatestTelemetry(asset2.getId(), "temperatureComputed,humidityComputed"); assertThat(telemetry1).isNotNull(); assertThat(telemetry2).isNotNull(); assertThat(telemetry1.get("temperatureComputed").get(0).get("ts").asText()).isEqualTo(Long.toString(ts)); assertThat(telemetry1.get("temperatureComputed").get(0).get("value").asDouble()).isEqualTo(12.5); + assertThat(telemetry1.get("humidityComputed").get(0).get("ts").asText()).isEqualTo(Long.toString(ts)); + assertThat(telemetry1.get("humidityComputed").get(0).get("value").asDouble()).isEqualTo(85); assertThat(telemetry2.get("temperatureComputed").get(0).get("ts").asText()).isEqualTo(Long.toString(ts)); assertThat(telemetry2.get("temperatureComputed").get(0).get("value").asDouble()).isEqualTo(12.5); + assertThat(telemetry2.get("humidityComputed").get(0).get("ts").asText()).isEqualTo(Long.toString(ts)); + assertThat(telemetry2.get("humidityComputed").get(0).get("value").asDouble()).isEqualTo(85); }); String deleteUrl = String.format("/api/v2/relation?fromId=%s&fromType=%s&relationType=%s&toId=%s&toType=%s", @@ -1212,10 +1222,10 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes EntityRelation.CONTAINS_TYPE, device.getId().getId(), EntityType.DEVICE ); doDelete(deleteUrl).andExpect(status().isOk()); - doDelete("/api/plugins/telemetry/ASSET/" + asset1.getId() + "/timeseries/delete?keys=temperatureComputed&deleteAllDataForKeys=true").andExpect(status().isOk()); + doDelete("/api/plugins/telemetry/ASSET/" + asset1.getId() + "/timeseries/delete?keys=temperatureComputed,humidityComputed&deleteAllDataForKeys=true").andExpect(status().isOk()); - // Update telemetry on device - long newTs = System.currentTimeMillis() - 300000L; + // Update telemetry on the device + long newTs = ts + 300000L; postTelemetry(device.getId(), String.format("{\"ts\": %s, \"values\": {\"temperature\":25}}", newTs)); // --- Assert propagated calculation (arguments-only mode after update) --- @@ -1223,13 +1233,18 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes .atMost(TIMEOUT, TimeUnit.SECONDS) .pollInterval(POLL_INTERVAL, TimeUnit.SECONDS) .untilAsserted(() -> { - ObjectNode telemetry1 = getLatestTelemetry(asset1.getId(), "temperatureComputed"); - ObjectNode telemetry2 = getLatestTelemetry(asset2.getId(), "temperatureComputed"); + ObjectNode telemetry1 = getLatestTelemetry(asset1.getId(), "temperatureComputed,humidityComputed"); + ObjectNode telemetry2 = getLatestTelemetry(asset2.getId(), "temperatureComputed,humidityComputed"); assertThat(telemetry1).isNotNull(); assertThat(telemetry2).isNotNull(); assertThat(telemetry1.get("temperatureComputed").get(0).get("value")).isEqualTo(NullNode.instance); + assertThat(telemetry1.get("humidityComputed").get(0).get("value")).isEqualTo(NullNode.instance); + assertThat(telemetry2.get("temperatureComputed").get(0).get("ts").asText()).isEqualTo(Long.toString(newTs)); assertThat(telemetry2.get("temperatureComputed").get(0).get("value").asDouble()).isEqualTo(25); + // TS for humidity is not updated -> expected + assertThat(telemetry2.get("humidityComputed").get(0).get("ts").asText()).isEqualTo(Long.toString(ts)); + assertThat(telemetry2.get("humidityComputed").get(0).get("value").asDouble()).isEqualTo(85); }); Asset asset3 = createAsset("Propagated Asset 3", null); @@ -1241,10 +1256,12 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes .atMost(TIMEOUT, TimeUnit.SECONDS) .pollInterval(POLL_INTERVAL, TimeUnit.SECONDS) .untilAsserted(() -> { - ObjectNode telemetry = getLatestTelemetry(asset3.getId(), "temperatureComputed"); + ObjectNode telemetry = getLatestTelemetry(asset3.getId(), "temperatureComputed,humidityComputed"); assertThat(telemetry).isNotNull(); assertThat(telemetry.get("temperatureComputed").get(0).get("ts").asText()).isEqualTo(Long.toString(newTs)); assertThat(telemetry.get("temperatureComputed").get(0).get("value").asDouble()).isEqualTo(25); + assertThat(telemetry.get("humidityComputed").get(0).get("ts").asText()).isEqualTo(Long.toString(newTs)); + assertThat(telemetry.get("humidityComputed").get(0).get("value").asDouble()).isEqualTo(85); }); } diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java index 017afe9709..b9c6e17ded 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java @@ -1209,7 +1209,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { Map statesMap = (Map) ReflectionTestUtils.getField(processor, "states"); Awaitility.await("CF state for entity actor ready to refresh dynamic arguments").atMost(TIMEOUT, TimeUnit.SECONDS).until(() -> { CalculatedFieldState calculatedFieldState = statesMap.get(cfId); - boolean isReady = calculatedFieldState != null && ((GeofencingCalculatedFieldState) calculatedFieldState).getLastDynamicArgumentsRefreshTs() < + boolean isReady = calculatedFieldState != null && ((GeofencingCalculatedFieldState) calculatedFieldState).getLastScheduledRefreshTs() < System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(scheduledUpdateInterval); log.warn("entityId {}, cfId {}, state ready to refresh == {}", entityId, cfId, isReady); return isReady; diff --git a/application/src/test/java/org/thingsboard/server/controller/AssetControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AssetControllerTest.java index e160fff59e..d039d7a26d 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AssetControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AssetControllerTest.java @@ -50,7 +50,7 @@ import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.dao.asset.AssetDao; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.service.DaoSqlTest; diff --git a/application/src/test/java/org/thingsboard/server/controller/AssetProfileControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AssetProfileControllerTest.java index 14ac8c91d1..a1efe16381 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AssetProfileControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AssetProfileControllerTest.java @@ -42,7 +42,7 @@ import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.dao.asset.AssetProfileDao; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.service.DaoSqlTest; import java.util.ArrayList; diff --git a/application/src/test/java/org/thingsboard/server/controller/CustomerControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/CustomerControllerTest.java index 95cf62f904..be99343459 100644 --- a/application/src/test/java/org/thingsboard/server/controller/CustomerControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/CustomerControllerTest.java @@ -44,7 +44,7 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.dao.customer.CustomerDao; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.service.DaoSqlTest; import java.util.ArrayList; diff --git a/application/src/test/java/org/thingsboard/server/controller/DashboardControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DashboardControllerTest.java index 052ff8777b..712b9f1472 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DashboardControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DashboardControllerTest.java @@ -51,7 +51,7 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.dao.dashboard.DashboardDao; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.service.DaoSqlTest; import java.util.ArrayList; diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java index 78679948ab..efbf18d5f5 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java @@ -72,7 +72,7 @@ import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportColumn import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportRequest; import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportResult; import org.thingsboard.server.dao.device.DeviceDao; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.service.DaoSqlTest; diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceProfileControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceProfileControllerTest.java index fb0e3fb57a..970461480d 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DeviceProfileControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceProfileControllerTest.java @@ -52,7 +52,7 @@ import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.dao.device.DeviceProfileDao; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.service.DaoSqlTest; import java.util.ArrayList; diff --git a/application/src/test/java/org/thingsboard/server/controller/EdgeControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/EdgeControllerTest.java index f1d3ef1d52..217bba43bb 100644 --- a/application/src/test/java/org/thingsboard/server/controller/EdgeControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/EdgeControllerTest.java @@ -72,7 +72,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.common.data.security.model.JwtSettings; import org.thingsboard.server.dao.edge.EdgeDao; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.edge.imitator.EdgeImitator; diff --git a/application/src/test/java/org/thingsboard/server/controller/EntityViewControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/EntityViewControllerTest.java index c9b63b01a6..fdff81799f 100644 --- a/application/src/test/java/org/thingsboard/server/controller/EntityViewControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/EntityViewControllerTest.java @@ -63,7 +63,7 @@ import org.thingsboard.server.common.data.query.EntityKeyType; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.dao.entityview.EntityViewDao; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.service.DaoSqlTest; diff --git a/application/src/test/java/org/thingsboard/server/controller/OtaPackageControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/OtaPackageControllerTest.java index 3fc839996c..7df2b84c43 100644 --- a/application/src/test/java/org/thingsboard/server/controller/OtaPackageControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/OtaPackageControllerTest.java @@ -37,7 +37,7 @@ import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.service.DaoSqlTest; import java.nio.ByteBuffer; diff --git a/application/src/test/java/org/thingsboard/server/controller/RuleChainControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/RuleChainControllerTest.java index 72a6697927..a02332812b 100644 --- a/application/src/test/java/org/thingsboard/server/controller/RuleChainControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/RuleChainControllerTest.java @@ -47,7 +47,7 @@ import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.security.Authority; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.rule.RuleChainDao; import org.thingsboard.server.dao.service.DaoSqlTest; diff --git a/application/src/test/java/org/thingsboard/server/controller/TbResourceControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/TbResourceControllerTest.java index 0875551112..5e880fb957 100644 --- a/application/src/test/java/org/thingsboard/server/controller/TbResourceControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/TbResourceControllerTest.java @@ -48,7 +48,7 @@ import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.widget.WidgetType; import org.thingsboard.server.common.data.widget.WidgetTypeDetails; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.service.DaoSqlTest; import java.util.ArrayList; diff --git a/application/src/test/java/org/thingsboard/server/controller/UserControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/UserControllerTest.java index f5f51a0c6b..8fef7f3e1d 100644 --- a/application/src/test/java/org/thingsboard/server/controller/UserControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/UserControllerTest.java @@ -50,7 +50,7 @@ import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.settings.StarredDashboardInfo; import org.thingsboard.server.common.data.settings.UserDashboardsInfo; import org.thingsboard.server.dao.device.DeviceService; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.user.UserDao; diff --git a/application/src/test/java/org/thingsboard/server/controller/WidgetsBundleControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/WidgetsBundleControllerTest.java index 0b1c2d282e..f84e5eb6e7 100644 --- a/application/src/test/java/org/thingsboard/server/controller/WidgetsBundleControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/WidgetsBundleControllerTest.java @@ -29,7 +29,7 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.widget.WidgetsBundle; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.service.DaoSqlTest; import java.util.ArrayList; diff --git a/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java index 84d879c993..ef11a4dcf8 100644 --- a/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java @@ -22,6 +22,7 @@ import com.google.protobuf.AbstractMessage; import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.MessageLite; import lombok.extern.slf4j.Slf4j; +import org.awaitility.Awaitility; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -102,6 +103,7 @@ import org.thingsboard.server.gen.edge.v1.UserUpdateMsg; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.TreeMap; import java.util.UUID; @@ -738,4 +740,27 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest { return rpc; } + protected void verifyEdgeDisconnected() { + verifyEdgeActiveFlag(false); + } + + protected void verifyEdgeConnected() { + verifyEdgeActiveFlag(true); + } + + private void verifyEdgeActiveFlag(boolean value) { + Awaitility.await() + .atMost(TIMEOUT, TimeUnit.SECONDS) + .until(() -> { + List> values = doGetAsyncTyped("/api/plugins/telemetry/EDGE/" + edge.getId() + + "/values/attributes/SERVER_SCOPE", new TypeReference<>() {}); + Optional> activeAttrOpt = values.stream().filter(att -> att.get("key").equals("active")).findFirst(); + if (activeAttrOpt.isEmpty()) { + return false; + } + Map activeAttr = activeAttrOpt.get(); + return Boolean.toString(value).equals(activeAttr.get("value").toString()); + }); + } + } diff --git a/application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java index 8d79416022..380dec8015 100644 --- a/application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java @@ -893,18 +893,7 @@ public class DeviceEdgeTest extends AbstractEdgeTest { ObjectNode attributes = JacksonUtil.newObjectNode(); attributes.put("active", true); doPost("/api/plugins/telemetry/EDGE/" + edge.getId() + "/attributes/" + DataConstants.SERVER_SCOPE, attributes); - Awaitility.await() - .atMost(TIMEOUT, TimeUnit.SECONDS) - .until(() -> { - List> values = doGetAsyncTyped("/api/plugins/telemetry/EDGE/" + edge.getId() + - "/values/attributes/SERVER_SCOPE", new TypeReference<>() {}); - Optional> activeAttrOpt = values.stream().filter(att -> att.get("key").equals("active")).findFirst(); - if (activeAttrOpt.isEmpty()) { - return false; - } - Map activeAttr = activeAttrOpt.get(); - return "true".equals(activeAttr.get("value").toString()); - }); + verifyEdgeConnected(); } } diff --git a/application/src/test/java/org/thingsboard/server/edge/DeviceProfileEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/DeviceProfileEdgeTest.java index a29d7a4672..cbac977835 100644 --- a/application/src/test/java/org/thingsboard/server/edge/DeviceProfileEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/DeviceProfileEdgeTest.java @@ -151,14 +151,20 @@ public class DeviceProfileEdgeTest extends AbstractEdgeTest { // delete profile when edge is offline edgeImitator.disconnect(); + verifyEdgeDisconnected(); + doDelete("/api/deviceProfile/" + deviceProfile.getUuidId()) .andExpect(status().isOk()); - edgeImitator.connect(); // 25 sync message - // + 2 RuleChain and RuleChainMetadata - // + 1 delete DeviceProfile + // + 1 RuleChain Added + // + 1 RuleChainMetadata Added + // + 1 DeviceProfile Delete edgeImitator.expectMessageAmount(SYNC_MESSAGE_COUNT + 3); + + edgeImitator.connect(); + verifyEdgeConnected(); + Assert.assertTrue(edgeImitator.waitForMessages()); latestMessage = edgeImitator.getLatestMessage(); diff --git a/application/src/test/java/org/thingsboard/server/edge/EdgeStatsIntegrationTest.java b/application/src/test/java/org/thingsboard/server/edge/EdgeStatsIntegrationTest.java new file mode 100644 index 0000000000..71722ad2a8 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/edge/EdgeStatsIntegrationTest.java @@ -0,0 +1,148 @@ +/** + * Copyright © 2016-2025 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.edge; + +import com.fasterxml.jackson.databind.JsonNode; +import lombok.extern.slf4j.Slf4j; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.common.data.edge.EdgeEvent; +import org.thingsboard.server.common.data.edge.EdgeEventActionType; +import org.thingsboard.server.common.data.edge.EdgeEventType; +import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.dao.edge.stats.EdgeStatsCounterService; +import org.thingsboard.server.dao.edge.stats.EdgeStatsKey; +import org.thingsboard.server.dao.edge.stats.MsgCounters; +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.service.edge.stats.EdgeStatsService; + +import java.time.Duration; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import static org.awaitility.Awaitility.await; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.thingsboard.server.dao.edge.stats.EdgeStatsKey.DOWNLINK_MSGS_ADDED; +import static org.thingsboard.server.dao.edge.stats.EdgeStatsKey.DOWNLINK_MSGS_PERMANENTLY_FAILED; +import static org.thingsboard.server.dao.edge.stats.EdgeStatsKey.DOWNLINK_MSGS_PUSHED; +import static org.thingsboard.server.dao.edge.stats.EdgeStatsKey.DOWNLINK_MSGS_TMP_FAILED; + +@DaoSqlTest +@Slf4j +public class EdgeStatsIntegrationTest extends AbstractEdgeTest { + + private static final String STATISTICS_DEVICE_PROFILE = "STATISTICS"; + + private static final long EXPECTED_MSGS_ADDED = 6L; + private static final long EXPECTED_MSGS_PUSHED = 6L; + private static final long EXPECTED_MSGS_PERMANENTLY_FAILED = 0L; + private static final long EXPECTED_MSGS_TMP_FAILED = 0L; + + @Autowired + private EdgeStatsService edgeStatsService; + @Autowired + private EdgeStatsCounterService statsCounterService; + + @Test + public void testReportStats() throws Exception { + // GIVEN + simulateEdgeEventsAddedDownlinkPushed(); + + // Await Edge Counters Updated + await().atMost(10, TimeUnit.SECONDS).pollInterval(Duration.ofMillis(200)).untilAsserted(() -> { + MsgCounters counters = statsCounterService.getMsgCountersByEdge().get(edge.getId()); + assertEquals(EXPECTED_MSGS_ADDED, counters.getMsgsAdded().get()); + assertEquals(EXPECTED_MSGS_PUSHED, counters.getMsgsPushed().get()); + assertEquals(EXPECTED_MSGS_PERMANENTLY_FAILED, counters.getMsgsPermanentlyFailed().get()); + assertEquals(EXPECTED_MSGS_TMP_FAILED, counters.getMsgsTmpFailed().get()); + }); + + Thread.sleep(1000); + + // WHEN + edgeStatsService.reportStats(); + + // THEN + await().atMost(10, TimeUnit.SECONDS).pollInterval(Duration.ofMillis(200)).untilAsserted(() -> { + List actualStats = fetchLatestStats(); + assertEquals(EXPECTED_MSGS_ADDED, getStatsLongValue(actualStats, DOWNLINK_MSGS_ADDED)); + assertEquals(EXPECTED_MSGS_PUSHED, getStatsLongValue(actualStats, DOWNLINK_MSGS_PUSHED)); + assertEquals(EXPECTED_MSGS_PERMANENTLY_FAILED, getStatsLongValue(actualStats, DOWNLINK_MSGS_PERMANENTLY_FAILED)); + assertEquals(EXPECTED_MSGS_TMP_FAILED, getStatsLongValue(actualStats, DOWNLINK_MSGS_TMP_FAILED)); + }); + } + + private long getStatsLongValue(List stats, EdgeStatsKey key) { + return stats.stream().filter(e -> e.getKey().equals(key.getKey())).findFirst().get().getLongValue().orElse(0L); + } + + private List fetchLatestStats() throws ExecutionException, InterruptedException { + return tsService.findLatest( + tenantId, + edge.getId(), + Arrays.stream(EdgeStatsKey.values()).map(EdgeStatsKey::getKey).toList()).get(); + } + + private void simulateEdgeEventsAddedDownlinkPushed() throws InterruptedException, ExecutionException { + statsCounterService.clear(edge.getId()); + + // Save device and assign to edge + // 2 DOWNLINK_MSGS_ADDED, EdgeEvents: [{DEVICE_PROFILE: ADDED}, {DEVICE: ASSIGNED_TO_EDGE}] + // 2 DOWNLINK_MSGS_PUSHED, Downlinks: [{deviceProfileUpdateMsg}, {deviceUpdateMsg, deviceProfileUpdateMsg, deviceCredentialsUpdateMsg}] + edgeImitator.expectMessageAmount(4); + Device savedDevice = saveDevice("StatisticDevice", STATISTICS_DEVICE_PROFILE); + doPost("/api/edge/" + edge.getUuidId() + "/device/" + savedDevice.getUuidId(), Device.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + + // Save asset and assign to edge + // 1 DOWNLINK_MSGS_ADDED, EdgeEvents: [{ASSET: ASSIGNED_TO_EDGE}] + // 1 DOWNLINK_MSGS_PUSHED, Downlinks: [{assetUpdateMsg, assetProfileUpdateMsg}] + edgeImitator.expectMessageAmount(2); + Asset savedAsset = saveAsset("Edge Asset"); + doPost("/api/edge/" + edge.getUuidId() + + "/asset/" + savedAsset.getUuidId(), Asset.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + + // Create customer and assign edge to the customer + // 2 DOWNLINK_MSGS_ADDED, EdgeEvents: [{CUSTOMER: ADDED}, {EDGE: ASSIGNED_TO_CUSTOMER}] + // 2 DOWNLINK_MSGS_PUSHED, Downlinks: [{customerUpdateMsg}, {edgeConfiguration}] + edgeImitator.expectMessageAmount(2); + Customer customer = new Customer(); + customer.setTitle("Edge Customer"); + Customer savedCustomer = doPost("/api/customer", customer, Customer.class); + doPost("/api/customer/" + savedCustomer.getUuidId() + + "/edge/" + edge.getUuidId(), Edge.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + + // Send device telemetry downlink for the device + // 1 DOWNLINK_MSGS_ADDED, EdgeEvents: [{DEVICE: TIMESERIES_UPDATED}] + // 1 DOWNLINK_MSGS_PUSHED, Downlinks: [{entityData}] + edgeImitator.expectMessageAmount(1); + String timeseriesData = "{\"data\":{\"temperature\":25},\"ts\":" + System.currentTimeMillis() + "}"; + JsonNode timeseriesEntityData = JacksonUtil.toJsonNode(timeseriesData); + EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.TIMESERIES_UPDATED, savedDevice.getId().getId(), EdgeEventType.DEVICE, timeseriesEntityData); + edgeEventService.saveAsync(edgeEvent).get(); + Assert.assertTrue(edgeImitator.waitForMessages()); + } +} diff --git a/application/src/test/java/org/thingsboard/server/edge/TelemetryEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/TelemetryEdgeTest.java index 36d9ed5308..a9ec11e9b1 100644 --- a/application/src/test/java/org/thingsboard/server/edge/TelemetryEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/TelemetryEdgeTest.java @@ -223,8 +223,10 @@ public class TelemetryEdgeTest extends AbstractEdgeTest { device.getId().getId(), EdgeEventType.DEVICE, timeseriesEntityData); edgeEventService.saveAsync(failedEdgeEvent).get(); + // add unique body to avoid merge and filter by device id in edge service (mergeAndFilterDownlinkDuplicates) + JsonNode uniqueBody = JacksonUtil.toJsonNode("{\"idx\":" + idx + "}"); EdgeEvent successEdgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.UPDATED, - device.getId().getId(), EdgeEventType.DEVICE, null); + device.getId().getId(), EdgeEventType.DEVICE, uniqueBody); edgeEventService.saveAsync(successEdgeEvent).get(); } diff --git a/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/PropagationArgumentEntryTest.java b/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/PropagationArgumentEntryTest.java index bf6a112e72..12f3e4298d 100644 --- a/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/PropagationArgumentEntryTest.java +++ b/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/PropagationArgumentEntryTest.java @@ -104,19 +104,19 @@ public class PropagationArgumentEntryTest { @Test void testUpdateEntryWhenAdded() { var added = new PropagationArgumentEntry(); - added.setAdded(ENTITY_3_ID); + added.setAdded(List.of(ENTITY_3_ID)); boolean changed = entry.updateEntry(added); assertThat(changed).isTrue(); assertThat(entry.getEntityIds()).containsExactlyInAnyOrder(ENTITY_1_ID, ENTITY_2_ID, ENTITY_3_ID); - assertThat(entry.getAdded()).isEqualTo(ENTITY_3_ID); + assertThat(entry.getAdded()).isEqualTo(List.of(ENTITY_3_ID)); } @Test void testUpdateEntryWhenAddedExistingEntity() { var added = new PropagationArgumentEntry(); - added.setAdded(ENTITY_2_ID); + added.setAdded(List.of(ENTITY_2_ID)); boolean changed = entry.updateEntry(added); @@ -149,6 +149,77 @@ public class PropagationArgumentEntryTest { assertThat(entry.getRemoved()).isNull(); } + @Test + void testUpdateEntryWhenPartitionStateRestoreAddsMissingIds() { + var restore = new PropagationArgumentEntry(List.of(ENTITY_1_ID, ENTITY_2_ID, ENTITY_3_ID)); + restore.setIgnoreRemovedEntities(true); + + boolean changed = entry.updateEntry(restore); + + assertThat(changed).isTrue(); + assertThat(entry.getEntityIds()).containsExactlyInAnyOrder(ENTITY_1_ID, ENTITY_2_ID, ENTITY_3_ID); + assertThat(entry.getAdded()).containsExactly(ENTITY_3_ID); + assertThat(entry.getRemoved()).isNull(); + assertThat(entry.isIgnoreRemovedEntities()).isFalse(); + } + + @Test + void testUpdateEntryWhenPartitionStateRestoreRemovesStaleIds() { + var restore = new PropagationArgumentEntry(List.of(ENTITY_1_ID)); + restore.setIgnoreRemovedEntities(true); + + boolean changed = entry.updateEntry(restore); + + assertThat(changed).isFalse(); // expected no change, since we consider the removal of stale ids as no-op + assertThat(entry.getEntityIds()).containsExactlyInAnyOrder(ENTITY_1_ID); + assertThat(entry.getAdded()).isNull(); + assertThat(entry.getRemoved()).isNull(); + assertThat(entry.isIgnoreRemovedEntities()).isFalse(); + } + + @Test + void testUpdateEntryWhenPartitionStateRestoreAddsAndRemoves() { + var restore = new PropagationArgumentEntry(List.of(ENTITY_1_ID, ENTITY_3_ID)); + restore.setIgnoreRemovedEntities(true); + + boolean changed = entry.updateEntry(restore); + + assertThat(changed).isTrue(); + assertThat(entry.getEntityIds()).containsExactlyInAnyOrder(ENTITY_1_ID, ENTITY_3_ID); + assertThat(entry.getAdded()).containsExactly(ENTITY_3_ID); + assertThat(entry.getRemoved()).isNull(); + assertThat(entry.isIgnoreRemovedEntities()).isFalse(); + } + + + @Test + void testUpdateEntryWhenPartitionStateRestoreNoChanges() { + var restore = new PropagationArgumentEntry(List.of(ENTITY_1_ID, ENTITY_2_ID)); + restore.setIgnoreRemovedEntities(true); + + boolean changed = entry.updateEntry(restore); + + assertThat(changed).isFalse(); + assertThat(entry.getEntityIds()).containsExactlyInAnyOrder(ENTITY_1_ID, ENTITY_2_ID); + assertThat(entry.getAdded()).isNull(); + assertThat(entry.getRemoved()).isNull(); + assertThat(entry.isIgnoreRemovedEntities()).isFalse(); + } + + @Test + void testUpdateEntryWhenPartitionStateRestoreEmptySet() { + var restore = new PropagationArgumentEntry(List.of()); + restore.setIgnoreRemovedEntities(true); + + boolean changed = entry.updateEntry(restore); + + assertThat(changed).isFalse(); // expected no change, since we consider the removal of stale ids as no-op + assertThat(entry.getEntityIds()).isEmpty(); + assertThat(entry.getAdded()).isNull(); + assertThat(entry.getRemoved()).isNull(); + assertThat(entry.isIgnoreRemovedEntities()).isFalse(); + } + @Test @SuppressWarnings("unchecked") void testToTbelCfArgWithValues() { diff --git a/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/PropagationCalculatedFieldStateTest.java b/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/PropagationCalculatedFieldStateTest.java index add6c1ee39..a9bf6d54d1 100644 --- a/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/PropagationCalculatedFieldStateTest.java +++ b/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/PropagationCalculatedFieldStateTest.java @@ -47,6 +47,7 @@ import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.relation.RelationPathLevel; import org.thingsboard.server.common.stats.DefaultStatsFactory; import org.thingsboard.server.dao.usagerecord.ApiLimitService; +import org.thingsboard.server.service.cf.CalculatedFieldProcessingService; import org.thingsboard.server.service.cf.PropagationCalculatedFieldResult; import org.thingsboard.server.service.cf.TelemetryCalculatedFieldResult; import org.thingsboard.server.service.cf.ctx.state.propagation.PropagationArgumentEntry; @@ -57,12 +58,17 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.thingsboard.server.common.data.cf.configuration.PropagationCalculatedFieldConfiguration.PROPAGATION_CONFIG_ARGUMENT; @@ -70,17 +76,26 @@ import static org.thingsboard.server.common.data.cf.configuration.PropagationCal public class PropagationCalculatedFieldStateTest { private static final String TEMPERATURE_ARGUMENT_NAME = "t"; + private static final String HUMIDITY_ARGUMENT_NAME = "h"; private static final String TEST_RESULT_EXPRESSION_KEY = "testResult"; private static final double TEMPERATURE_VALUE = 12.5; + private static final double HUMIDITY_VALUE = 85; + + private static final PropagationArgumentEntry EMPTY_PROPAGATION_ARGUMENT = new PropagationArgumentEntry(Collections.emptyList()); private final TenantId TENANT_ID = TenantId.fromUUID(UUID.fromString("6c3513cb-85e7-4510-8746-1ba01859a8ce")); private final DeviceId DEVICE_ID = new DeviceId(UUID.fromString("be960a50-c029-4698-b2ec-c56a543c561c")); private final AssetId ASSET_ID_1 = new AssetId(UUID.fromString("d26f0e5b-7d7d-4a61-9f5e-08ab97b30734")); private final AssetId ASSET_ID_2 = new AssetId(UUID.fromString("1933a317-4df5-4d36-9800-68aded74579b")); - private final SingleValueArgumentEntry singleValueArgEntry = + private final SingleValueArgumentEntry EMPTY_SINGLE_VALUE_ARGUMENT = new SingleValueArgumentEntry(); + + private final SingleValueArgumentEntry temperatureArgumentEntry = new SingleValueArgumentEntry(System.currentTimeMillis(), new DoubleDataEntry("temperature", TEMPERATURE_VALUE), 99L); + private final SingleValueArgumentEntry humidityArgumentEntry = + new SingleValueArgumentEntry(System.currentTimeMillis(), new DoubleDataEntry("humidity", HUMIDITY_VALUE), 99L); + private final PropagationArgumentEntry propagationArgEntry = new PropagationArgumentEntry(new ArrayList<>(List.of(ASSET_ID_2, ASSET_ID_1))); @@ -96,15 +111,19 @@ public class PropagationCalculatedFieldStateTest { @MockitoBean private ActorSystemContext actorSystemContext; + @MockitoBean + private CalculatedFieldProcessingService cfProcessingService; + @BeforeEach void setUp() { when(actorSystemContext.getTbelInvokeService()).thenReturn(tbelInvokeService); when(actorSystemContext.getApiLimitService()).thenReturn(apiLimitService); + when(actorSystemContext.getCalculatedFieldProcessingService()).thenReturn(cfProcessingService); when(apiLimitService.getLimit(any(), any())).thenReturn(1000L); } void initCtxAndState(boolean applyExpressionToResolvedArguments) { - ctx = new CalculatedFieldCtx(getCalculatedField(applyExpressionToResolvedArguments), actorSystemContext); + ctx = spy(new CalculatedFieldCtx(getCalculatedField(applyExpressionToResolvedArguments), actorSystemContext)); ctx.init(); state = new PropagationCalculatedFieldState(ctx.getEntityId()); @@ -121,7 +140,7 @@ public class PropagationCalculatedFieldStateTest { @Test void testInitAddsRequiredArgument() { initCtxAndState(false); - assertThat(state.getRequiredArguments()).containsExactlyInAnyOrder(TEMPERATURE_ARGUMENT_NAME, PROPAGATION_CONFIG_ARGUMENT); + assertThat(state.getRequiredArguments()).containsExactlyInAnyOrder(TEMPERATURE_ARGUMENT_NAME, HUMIDITY_ARGUMENT_NAME, PROPAGATION_CONFIG_ARGUMENT); } @Test @@ -133,7 +152,7 @@ public class PropagationCalculatedFieldStateTest { private static Stream provideInvalidPropagationArgs() { return Stream.of( null, - new PropagationArgumentEntry(Collections.emptyList()) + EMPTY_PROPAGATION_ARGUMENT ); } @@ -143,7 +162,8 @@ public class PropagationCalculatedFieldStateTest { initCtxAndState(false); Map args = new HashMap<>(); - args.put(TEMPERATURE_ARGUMENT_NAME, singleValueArgEntry); // Valid user arg + args.put(TEMPERATURE_ARGUMENT_NAME, temperatureArgumentEntry); // Valid user arg + args.put(HUMIDITY_ARGUMENT_NAME, humidityArgumentEntry); // Valid user arg if (propagationEntry != null) { args.put(PROPAGATION_CONFIG_ARGUMENT, propagationEntry); @@ -155,19 +175,54 @@ public class PropagationCalculatedFieldStateTest { } @Test - void testIsReadyWhenPropagationArgHasEntities() { + void testIsReadyWithoutExpressionWhenAllArgumentsAreNotEmpty() { initCtxAndState(false); - state.update(Map.of(TEMPERATURE_ARGUMENT_NAME, singleValueArgEntry, PROPAGATION_CONFIG_ARGUMENT, propagationArgEntry), ctx); + Map updatedArgs = Map.of( + TEMPERATURE_ARGUMENT_NAME, temperatureArgumentEntry, + HUMIDITY_ARGUMENT_NAME, humidityArgumentEntry, + PROPAGATION_CONFIG_ARGUMENT, propagationArgEntry + ); + state.update(updatedArgs, ctx); assertThat(state.isReady()).isTrue(); assertThat(state.getReadinessStatus().errorMsg()).isNull(); } + @Test + void testIsReadyWithoutExpressionWhenAtLeastOneArgumentIsNotEmpty() { + initCtxAndState(false); + Map updatedArgs = Map.of( + TEMPERATURE_ARGUMENT_NAME, temperatureArgumentEntry, + HUMIDITY_ARGUMENT_NAME, EMPTY_SINGLE_VALUE_ARGUMENT, + PROPAGATION_CONFIG_ARGUMENT, propagationArgEntry); + state.update(updatedArgs, ctx); + assertThat(state.isReady()).isTrue(); + assertThat(state.getReadinessStatus().errorMsg()).isNull(); + } + + @Test + void testIsNotReadyWithExpressionWhenAtLeastOneArgumentIsEmpty() { + initCtxAndState(true); + Map updatedArgs = Map.of( + TEMPERATURE_ARGUMENT_NAME, temperatureArgumentEntry, + HUMIDITY_ARGUMENT_NAME, EMPTY_SINGLE_VALUE_ARGUMENT, + PROPAGATION_CONFIG_ARGUMENT, propagationArgEntry); + state.update(updatedArgs, ctx); + assertThat(state.isReady()).isFalse(); + assertThat(state.getReadinessStatus().errorMsg()).isEqualTo("Required arguments are missing: h"); + } + @Test void testPerformCalculationWithEmptyPropagationArg() throws Exception { initCtxAndState(false); - state.getArguments().put(PROPAGATION_CONFIG_ARGUMENT, new PropagationArgumentEntry(Collections.emptyList())); + Map initArgs = Map.of( + TEMPERATURE_ARGUMENT_NAME, temperatureArgumentEntry, + HUMIDITY_ARGUMENT_NAME, humidityArgumentEntry, + PROPAGATION_CONFIG_ARGUMENT, EMPTY_PROPAGATION_ARGUMENT); + state.update(initArgs, ctx); + assertThat(state.isReady()).isFalse(); + // test empty propagation argument calculation PropagationCalculatedFieldResult result = performCalculation(); assertThat(result).isNotNull(); @@ -178,8 +233,12 @@ public class PropagationCalculatedFieldStateTest { @Test void testPerformCalculationWithArgumentsOnlyMode() throws Exception { initCtxAndState(false); - state.getArguments().put(PROPAGATION_CONFIG_ARGUMENT, propagationArgEntry); - state.getArguments().put(TEMPERATURE_ARGUMENT_NAME, singleValueArgEntry); + Map initArgs = Map.of( + TEMPERATURE_ARGUMENT_NAME, temperatureArgumentEntry, + HUMIDITY_ARGUMENT_NAME, EMPTY_SINGLE_VALUE_ARGUMENT, + PROPAGATION_CONFIG_ARGUMENT, propagationArgEntry); + state.update(initArgs, ctx); + assertThat(state.isReady()).isTrue(); PropagationCalculatedFieldResult propagationResult = performCalculation(); @@ -193,7 +252,7 @@ public class PropagationCalculatedFieldStateTest { assertThat(result.getScope()).isEqualTo(AttributeScope.SERVER_SCOPE); ObjectNode expectedNode = JacksonUtil.newObjectNode(); - JacksonUtil.addKvEntry(expectedNode, singleValueArgEntry.getKvEntryValue(), TEMPERATURE_ARGUMENT_NAME); + JacksonUtil.addKvEntry(expectedNode, temperatureArgumentEntry.getKvEntryValue(), TEMPERATURE_ARGUMENT_NAME); assertThat(result.getResult()).isEqualTo(expectedNode); } @@ -201,9 +260,13 @@ public class PropagationCalculatedFieldStateTest { @Test void testPerformCalculationWithExpressionResultMode() throws Exception { initCtxAndState(true); - state.getArguments().put(PROPAGATION_CONFIG_ARGUMENT, propagationArgEntry); - state.getArguments().put(TEMPERATURE_ARGUMENT_NAME, singleValueArgEntry); - + Map initArgs = Map.of( + TEMPERATURE_ARGUMENT_NAME, temperatureArgumentEntry, + HUMIDITY_ARGUMENT_NAME, humidityArgumentEntry, + PROPAGATION_CONFIG_ARGUMENT, propagationArgEntry + ); + state.update(initArgs, ctx); + assertThat(state.isReady()).isTrue(); PropagationCalculatedFieldResult propagationResult = performCalculation(); assertThat(propagationResult).isNotNull(); @@ -216,7 +279,7 @@ public class PropagationCalculatedFieldStateTest { assertThat(result.getScope()).isEqualTo(AttributeScope.SERVER_SCOPE); ObjectNode expectedNode = JacksonUtil.newObjectNode(); - expectedNode.put(TEST_RESULT_EXPRESSION_KEY, TEMPERATURE_VALUE * 2); + expectedNode.put(TEST_RESULT_EXPRESSION_KEY, (TEMPERATURE_VALUE + HUMIDITY_VALUE) / 2); assertThat(result.getResult()).isEqualTo(expectedNode); } @@ -224,18 +287,52 @@ public class PropagationCalculatedFieldStateTest { @Test void testPropagationWithUpdatedPropagationArgument() throws ExecutionException, InterruptedException { initCtxAndState(false); - state.getArguments().put(PROPAGATION_CONFIG_ARGUMENT, propagationArgEntry); - state.getArguments().put(TEMPERATURE_ARGUMENT_NAME, singleValueArgEntry); + Map initArgs = Map.of( + TEMPERATURE_ARGUMENT_NAME, temperatureArgumentEntry, + HUMIDITY_ARGUMENT_NAME, EMPTY_SINGLE_VALUE_ARGUMENT, + PROPAGATION_CONFIG_ARGUMENT, propagationArgEntry + ); + state.update(initArgs, ctx); + assertThat(state.isReady()).isTrue(); AssetId newEntityId = new AssetId(UUID.fromString("83e2c962-eeae-4708-984e-e6a24760f9c3")); PropagationArgumentEntry propagationArgumentEntry = new PropagationArgumentEntry(); - propagationArgumentEntry.setAdded(newEntityId); + propagationArgumentEntry.setAdded(List.of(newEntityId)); Map updated = state.update(Map.of(PROPAGATION_CONFIG_ARGUMENT, propagationArgumentEntry), ctx); assertThat(updated).isNotNull().containsEntry(PROPAGATION_CONFIG_ARGUMENT, propagationArgumentEntry); PropagationCalculatedFieldResult propagationCalculatedFieldResult = performCalculation(updated); assertThat(propagationCalculatedFieldResult).isNotNull(); assertThat(propagationCalculatedFieldResult.getEntityIds()).isNotNull().containsExactly(newEntityId); + assertThat(propagationCalculatedFieldResult.getResult()).isNotNull(); + assertThat(propagationCalculatedFieldResult.getResult().getResult()).isNotNull(); + assertThat(propagationCalculatedFieldResult.getResult().getResult()).isEqualTo(JacksonUtil.newObjectNode().put(TEMPERATURE_ARGUMENT_NAME, TEMPERATURE_VALUE)); + } + + @Test + void testPropapagationStateInitWithRestoredSetToFalse() { + initCtxAndState(false); + verify(cfProcessingService, never()).fetchPropagationArgumentFromDb(any(), any()); + verify(ctx, never()).scheduleReevaluation(anyLong(), any()); + } + + @Test + void testPropapagationStateInitWithRestoredSetToTrue() { + initCtxAndState(false); + Map initArgs = Map.of( + TEMPERATURE_ARGUMENT_NAME, temperatureArgumentEntry, + HUMIDITY_ARGUMENT_NAME, humidityArgumentEntry, + PROPAGATION_CONFIG_ARGUMENT, new PropagationArgumentEntry(Collections.emptyList()) + ); + state.update(initArgs, ctx); + assertThat(state.isReady()).isFalse(); + + when(cfProcessingService.fetchPropagationArgumentFromDb(any(), any())).thenReturn(Optional.of(propagationArgEntry)); + + state.init(true); + + verify(cfProcessingService).fetchPropagationArgumentFromDb(ctx, state.getEntityId()); + verify(ctx).scheduleReevaluation(0L, state.getActorCtx()); } private CalculatedField getCalculatedField(boolean applyExpressionToResolvedArguments) { @@ -260,8 +357,12 @@ public class PropagationCalculatedFieldStateTest { ReferencedEntityKey tempKey = new ReferencedEntityKey("temperature", ArgumentType.TS_LATEST, null); temperatureArg.setRefEntityKey(tempKey); - config.setArguments(Map.of(TEMPERATURE_ARGUMENT_NAME, temperatureArg)); - config.setExpression("{" + TEST_RESULT_EXPRESSION_KEY + ": " + TEMPERATURE_ARGUMENT_NAME + " * 2}"); + Argument humidityArg = new Argument(); + ReferencedEntityKey humidityKey = new ReferencedEntityKey("humidity", ArgumentType.TS_LATEST, null); + humidityArg.setRefEntityKey(humidityKey); + + config.setArguments(Map.of(TEMPERATURE_ARGUMENT_NAME, temperatureArg, HUMIDITY_ARGUMENT_NAME, humidityArg)); + config.setExpression("return { " + TEST_RESULT_EXPRESSION_KEY + ": (" + TEMPERATURE_ARGUMENT_NAME + " + " + HUMIDITY_ARGUMENT_NAME + ") / 2};"); AttributesOutput output = new AttributesOutput(); output.setScope(AttributeScope.SERVER_SCOPE); diff --git a/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/RelatedEntitiesAggregationCalculatedFieldStateTest.java b/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/RelatedEntitiesAggregationCalculatedFieldStateTest.java index a93a6e8ddd..5085c64ef2 100644 --- a/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/RelatedEntitiesAggregationCalculatedFieldStateTest.java +++ b/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/RelatedEntitiesAggregationCalculatedFieldStateTest.java @@ -113,8 +113,10 @@ public class RelatedEntitiesAggregationCalculatedFieldStateTest { } @Test - void testIsReadyReturnFalseWhenNoArgumentsSet() { + void testIsReadyWhenNoRelatedEntities() { assertThat(state.isReady()).isFalse(); + assertThat(state.getReadinessStatus().errorMsg()) + .isEqualTo("No entities found via 'Aggregation path to related entities'. Verify the configured relation type and direction."); } @Test diff --git a/application/src/test/java/org/thingsboard/server/service/edge/EdgeMsgConstructorUtilsTest.java b/application/src/test/java/org/thingsboard/server/service/edge/EdgeMsgConstructorUtilsTest.java index 1ee6e31372..ebb8305871 100644 --- a/application/src/test/java/org/thingsboard/server/service/edge/EdgeMsgConstructorUtilsTest.java +++ b/application/src/test/java/org/thingsboard/server/service/edge/EdgeMsgConstructorUtilsTest.java @@ -15,10 +15,12 @@ */ package org.thingsboard.server.service.edge; +import com.fasterxml.jackson.databind.JsonNode; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -36,6 +38,10 @@ import org.thingsboard.rule.engine.rest.TbSendRestApiCallReplyNode; import org.thingsboard.rule.engine.telemetry.TbCalculatedFieldsNode; import org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode; import org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode; +import org.thingsboard.server.common.data.edge.EdgeEvent; +import org.thingsboard.server.common.data.edge.EdgeEventActionType; +import org.thingsboard.server.common.data.edge.EdgeEventType; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.gen.edge.v1.EdgeVersion; @@ -44,6 +50,7 @@ import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import java.lang.reflect.Constructor; import java.util.List; import java.util.Optional; +import java.util.UUID; import java.util.stream.Stream; import static org.thingsboard.server.service.edge.EdgeMsgConstructorUtils.EXCLUDED_NODES_BY_EDGE_VERSION; @@ -155,4 +162,95 @@ public class EdgeMsgConstructorUtilsTest { String.format("For EdgeVersion '%s', ruleNode '%s' should not be included.", edgeVersion, ruleNode.getType())); } + @Test + @DisplayName("mergeDownlinkDuplicates: latest per attribute key is retained and duplicates removed") + public void testMergeDownlinkDuplicates() { + UUID deviceId = UUID.randomUUID(); + UUID assetId = UUID.randomUUID(); + TenantId tenantId = TenantId.fromUUID(UUID.randomUUID()); + + var deviceAttrUpdate1 = createEdgeEvent(tenantId, 1, EdgeEventActionType.ATTRIBUTES_UPDATED, + deviceId, EdgeEventType.DEVICE, createAttrBody(1_000L, "{\"a\":1,\"b\":1,\"d\":1}")); + var deviceAttrUpdate2 = createEdgeEvent(tenantId, 2, EdgeEventActionType.ATTRIBUTES_UPDATED, + deviceId, EdgeEventType.DEVICE, createAttrBody(2_000L, "{\"a\":2,\"b\":2,\"c\":2}")); + var deviceAttrUpdate3 = createEdgeEvent(tenantId, 3, EdgeEventActionType.ATTRIBUTES_UPDATED, + deviceId, EdgeEventType.DEVICE, createAttrBody(3_000L, "{\"a\":3,\"d\":3}")); + + var deviceUpdate = createEdgeEvent(tenantId, 4, EdgeEventActionType.UPDATED, + deviceId, EdgeEventType.DEVICE, null); + var deviceUpdateDup = createEdgeEvent(tenantId, 5, EdgeEventActionType.UPDATED, + deviceId, EdgeEventType.DEVICE, null); + + var assetAttrUpdate1 = createEdgeEvent(tenantId, 6, EdgeEventActionType.ATTRIBUTES_UPDATED, + assetId, EdgeEventType.ASSET, createAttrBody(6_000L, "{\"a\":6,\"d\":6}")); + var assetAttrUpdate2 = createEdgeEvent(tenantId, 7, EdgeEventActionType.ATTRIBUTES_UPDATED, + assetId, EdgeEventType.ASSET, createAttrBody(7_000L, "{\"a\":7,\"b\":7,\"c\":7}")); + var assetAttrUpdate3 = createEdgeEvent(tenantId, 8, EdgeEventActionType.ATTRIBUTES_UPDATED, + assetId, EdgeEventType.ASSET, createAttrBody(8_000L, "{\"a\":8,\"d\":8}")); + + List input = List.of(deviceAttrUpdate1, deviceAttrUpdate2, deviceAttrUpdate3, + deviceUpdate, deviceUpdateDup, + assetAttrUpdate1, assetAttrUpdate2, assetAttrUpdate3); + List merged = EdgeMsgConstructorUtils.mergeAndFilterDownlinkDuplicates(input); + + Assertions.assertEquals(5, merged.size()); + + EdgeEvent deviceMergedAttrBC = merged.get(0); + Assertions.assertEquals(2, deviceMergedAttrBC.getSeqId()); + Assertions.assertEquals(deviceId, deviceMergedAttrBC.getEntityId()); + Assertions.assertEquals(2_000L, deviceMergedAttrBC.getBody().get("ts").asLong()); + Assertions.assertEquals(2, getIntValue(deviceMergedAttrBC.getBody(), "b")); + Assertions.assertEquals(2, getIntValue(deviceMergedAttrBC.getBody(), "c")); + Assertions.assertNull(getIntValue(deviceMergedAttrBC.getBody(), "a")); + + EdgeEvent deviceMergedAttrAD = merged.get(1); + Assertions.assertEquals(3, deviceMergedAttrAD.getSeqId()); + Assertions.assertEquals(deviceId, deviceMergedAttrAD.getEntityId()); + Assertions.assertEquals(3_000L, deviceMergedAttrAD.getBody().get("ts").asLong()); + Assertions.assertEquals(3, getIntValue(deviceMergedAttrAD.getBody(), "a")); + Assertions.assertEquals(3, getIntValue(deviceMergedAttrAD.getBody(), "d")); + + EdgeEvent mergedDeviceUpdate = merged.get(2); + Assertions.assertEquals(4, mergedDeviceUpdate.getSeqId()); + Assertions.assertEquals(EdgeEventActionType.UPDATED, mergedDeviceUpdate.getAction()); + + EdgeEvent assetMergedAttrBC = merged.get(3); + Assertions.assertEquals(7, assetMergedAttrBC.getSeqId()); + Assertions.assertEquals(assetId, assetMergedAttrBC.getEntityId()); + Assertions.assertEquals(7_000L, assetMergedAttrBC.getBody().get("ts").asLong()); + Assertions.assertEquals(7, getIntValue(assetMergedAttrBC.getBody(), "b")); + Assertions.assertEquals(7, getIntValue(assetMergedAttrBC.getBody(), "c")); + Assertions.assertNull(getIntValue(assetMergedAttrBC.getBody(), "a")); + + EdgeEvent assetMergedAttrAD = merged.get(4); + Assertions.assertEquals(8, assetMergedAttrAD.getSeqId()); + Assertions.assertEquals(assetId, assetMergedAttrAD.getEntityId()); + Assertions.assertEquals(8_000L, assetMergedAttrAD.getBody().get("ts").asLong()); + Assertions.assertEquals(8, getIntValue(assetMergedAttrAD.getBody(), "a")); + Assertions.assertEquals(8, getIntValue(assetMergedAttrAD.getBody(), "d")); + } + + private Integer getIntValue(JsonNode body, String key) { + return body.get("kv").get(key) != null ? body.get("kv").get(key).asInt() : null; + } + + private static JsonNode createAttrBody(long ts, String kvJson) { + return JacksonUtil.toJsonNode("{\"ts\":" + ts + ",\"kv\":" + kvJson + "}"); + } + + private static EdgeEvent createEdgeEvent(TenantId tenantId, + long seqId, + EdgeEventActionType action, + UUID entityId, + EdgeEventType type, + JsonNode body) { + EdgeEvent edgeEvent = new EdgeEvent(); + edgeEvent.setSeqId(seqId); + edgeEvent.setTenantId(tenantId); + edgeEvent.setAction(action); + edgeEvent.setEntityId(entityId); + edgeEvent.setType(type); + edgeEvent.setBody(body); + return edgeEvent; + } } diff --git a/application/src/test/java/org/thingsboard/server/service/edge/EdgeStatsTest.java b/application/src/test/java/org/thingsboard/server/service/edge/EdgeStatsTest.java index 25ff0f1b5d..3cb4bec803 100644 --- a/application/src/test/java/org/thingsboard/server/service/edge/EdgeStatsTest.java +++ b/application/src/test/java/org/thingsboard/server/service/edge/EdgeStatsTest.java @@ -21,6 +21,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.test.util.ReflectionTestUtils; @@ -44,9 +45,11 @@ import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; +import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.thingsboard.server.dao.edge.stats.EdgeStatsKey.DOWNLINK_MSGS_ADDED; @@ -58,6 +61,16 @@ import static org.thingsboard.server.dao.edge.stats.EdgeStatsKey.DOWNLINK_MSGS_T @ExtendWith(MockitoExtension.class) public class EdgeStatsTest { + private static final int TTL_DAYS = 30; + private static final long REPORT_INTERVAL_MILLIS = 600_000L; + + private static final long EXPECTED_MSGS_ADDED = 5L; + private static final long EXPECTED_MSGS_PUSHED = 3L; + private static final long EXPECTED_MSGS_PERMANENTLY_FAILED = 1L; + private static final long EXPECTED_MSGS_TMP_FAILED = 0L; + private static final long EXPECTED_MSGS_LAG = 10L; + private static final long EXPECTED_MSGS_KAFKA_LAG = 15L; + @Mock private TimeseriesService tsService; @Mock @@ -66,108 +79,100 @@ public class EdgeStatsTest { private EdgeStatsCounterService statsCounterService; private EdgeStatsService edgeStatsService; + @Captor + private ArgumentCaptor> captor; + private final TenantId tenantId = TenantId.fromUUID(UUID.randomUUID()); private final EdgeId edgeId = new EdgeId(UUID.randomUUID()); @BeforeEach void setUp() { - edgeStatsService = new EdgeStatsService( + edgeStatsService = createEdgeStatsService(Optional.empty()); + } + + private EdgeStatsService createEdgeStatsService(Optional kafkaAdmin) { + EdgeStatsService service = new EdgeStatsService( tsService, statsCounterService, topicService, - Optional.empty() + kafkaAdmin ); - - ReflectionTestUtils.setField(edgeStatsService, "edgesStatsTtlDays", 30); - ReflectionTestUtils.setField(edgeStatsService, "reportIntervalMillis", 600_000L); + ReflectionTestUtils.setField(service, "edgesStatsTtlDays", TTL_DAYS); + ReflectionTestUtils.setField(service, "reportIntervalMillis", REPORT_INTERVAL_MILLIS); + return service; } @Test public void testReportStatsSavesTelemetry() { - // given + // GIVEN + setupCounters(); + + // WHEN + edgeStatsService.reportStats(); + + // THEN + Map counters = verifyCounters(); + Assertions.assertEquals(EXPECTED_MSGS_LAG, counters.get(DOWNLINK_MSGS_LAG.getKey()).longValue()); + } + + @Test + public void testReportStatsWithKafkaLag() { + // GIVEN + setupCounters(); + setupKafkaLag(); + + // WHEN + edgeStatsService.reportStats(); + + // THEN + Map valuesByKey = verifyCounters(); + Assertions.assertEquals(EXPECTED_MSGS_KAFKA_LAG, valuesByKey.get(DOWNLINK_MSGS_LAG.getKey())); + } + + private void setupCounters() { MsgCounters counters = new MsgCounters(tenantId); - counters.getMsgsAdded().set(5); - counters.getMsgsPushed().set(3); - counters.getMsgsPermanentlyFailed().set(1); - counters.getMsgsTmpFailed().set(0); - counters.getMsgsLag().set(10); + counters.getMsgsAdded().set(EXPECTED_MSGS_ADDED); + counters.getMsgsPushed().set(EXPECTED_MSGS_PUSHED); + counters.getMsgsPermanentlyFailed().set(EXPECTED_MSGS_PERMANENTLY_FAILED); + counters.getMsgsTmpFailed().set(EXPECTED_MSGS_TMP_FAILED); + counters.getMsgsLag().set(EXPECTED_MSGS_LAG); ConcurrentHashMap countersByEdge = new ConcurrentHashMap<>(); countersByEdge.put(edgeId, counters); - when(statsCounterService.getCounterByEdge()).thenReturn(countersByEdge); + when(statsCounterService.getMsgCountersByEdge()).thenReturn(countersByEdge); - ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); when(tsService.save(eq(tenantId), eq(edgeId), captor.capture(), anyLong())) .thenReturn(Futures.immediateFuture(mock(TimeseriesSaveResult.class))); + } - // when - edgeStatsService.reportStats(); + private Map verifyCounters() { + verify(tsService, times(1)).save(eq(tenantId), eq(edgeId), anyList(), anyLong()); + verify(statsCounterService, times(1)).clear(edgeId); - // then List entries = captor.getValue(); Assertions.assertEquals(5, entries.size()); Map valuesByKey = entries.stream() .collect(Collectors.toMap(TsKvEntry::getKey, e -> e.getLongValue().orElse(-1L))); - Assertions.assertEquals(5L, valuesByKey.get(DOWNLINK_MSGS_ADDED.getKey()).longValue()); - Assertions.assertEquals(3L, valuesByKey.get(DOWNLINK_MSGS_PUSHED.getKey()).longValue()); - Assertions.assertEquals(1L, valuesByKey.get(DOWNLINK_MSGS_PERMANENTLY_FAILED.getKey()).longValue()); - Assertions.assertEquals(0L, valuesByKey.get(DOWNLINK_MSGS_TMP_FAILED.getKey()).longValue()); - Assertions.assertEquals(10L, valuesByKey.get(DOWNLINK_MSGS_LAG.getKey()).longValue()); - - - verify(statsCounterService).clear(edgeId); + Assertions.assertEquals(EXPECTED_MSGS_ADDED, valuesByKey.get(DOWNLINK_MSGS_ADDED.getKey()).longValue()); + Assertions.assertEquals(EXPECTED_MSGS_PUSHED, valuesByKey.get(DOWNLINK_MSGS_PUSHED.getKey()).longValue()); + Assertions.assertEquals(EXPECTED_MSGS_PERMANENTLY_FAILED, valuesByKey.get(DOWNLINK_MSGS_PERMANENTLY_FAILED.getKey()).longValue()); + Assertions.assertEquals(EXPECTED_MSGS_TMP_FAILED, valuesByKey.get(DOWNLINK_MSGS_TMP_FAILED.getKey()).longValue()); + return valuesByKey; } - @Test - public void testReportStatsWithKafkaLag() { - // given - MsgCounters counters = new MsgCounters(tenantId); - counters.getMsgsAdded().set(2); - counters.getMsgsPushed().set(2); - counters.getMsgsPermanentlyFailed().set(0); - counters.getMsgsTmpFailed().set(1); - counters.getMsgsLag().set(0); - - ConcurrentHashMap countersByEdge = new ConcurrentHashMap<>(); - countersByEdge.put(edgeId, counters); - - // mocks - when(statsCounterService.getCounterByEdge()).thenReturn(countersByEdge); - + private void setupKafkaLag() { String topic = "edge-topic"; TopicPartitionInfo partitionInfo = new TopicPartitionInfo(topic, tenantId, 0, false); when(topicService.buildEdgeEventNotificationsTopicPartitionInfo(tenantId, edgeId)).thenReturn(partitionInfo); KafkaAdmin kafkaAdmin = mock(KafkaAdmin.class); when(kafkaAdmin.getTotalLagForGroupsBulk(Set.of(topic))) - .thenReturn(Map.of(topic, 15L)); - - ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); - when(tsService.save(eq(tenantId), eq(edgeId), captor.capture(), anyLong())) - .thenReturn(Futures.immediateFuture(mock(TimeseriesSaveResult.class))); - - edgeStatsService = new EdgeStatsService( - tsService, - statsCounterService, - topicService, - Optional.of(kafkaAdmin) - ); - ReflectionTestUtils.setField(edgeStatsService, "edgesStatsTtlDays", 30); - ReflectionTestUtils.setField(edgeStatsService, "reportIntervalMillis", 600_000L); - - // when - edgeStatsService.reportStats(); - - // then - List entries = captor.getValue(); - Map valuesByKey = entries.stream() - .collect(Collectors.toMap(TsKvEntry::getKey, e -> e.getLongValue().orElse(-1L))); + .thenReturn(Map.of(topic, EXPECTED_MSGS_KAFKA_LAG)); - Assertions.assertEquals(15L, valuesByKey.get(DOWNLINK_MSGS_LAG.getKey())); - verify(statsCounterService).clear(edgeId); + edgeStatsService = createEdgeStatsService(Optional.of(kafkaAdmin)); } } diff --git a/application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java b/application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java index a146730465..aadbb2ebb5 100644 --- a/application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java @@ -60,7 +60,7 @@ import org.thingsboard.server.common.data.widget.WidgetTypeDetails; import org.thingsboard.server.controller.AbstractControllerTest; import org.thingsboard.server.dao.ai.AiModelService; import org.thingsboard.server.dao.dashboard.DashboardService; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.resource.ResourceService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.service.DaoSqlTest; diff --git a/application/src/test/java/org/thingsboard/server/utils/CalculatedFieldUtilsTest.java b/application/src/test/java/org/thingsboard/server/utils/CalculatedFieldUtilsTest.java index 83538fe07c..f4bb9bc4c9 100644 --- a/application/src/test/java/org/thingsboard/server/utils/CalculatedFieldUtilsTest.java +++ b/application/src/test/java/org/thingsboard/server/utils/CalculatedFieldUtilsTest.java @@ -119,7 +119,7 @@ class CalculatedFieldUtilsTest { } @Test - void toProtoAndFromProto_shouldCreatePropagationStateWithEmptyPropagationArgument() { + void toProtoAndFromProto_shouldCreatePropagationStateWithNotEmptyPropagationArgument() { // given CalculatedFieldEntityCtxId stateId = mock(CalculatedFieldEntityCtxId.class); given(stateId.tenantId()).willReturn(TENANT_ID); @@ -158,8 +158,7 @@ class CalculatedFieldUtilsTest { assertThat(propagationState.getEntityId()).isEqualTo(DEVICE_ID); assertThat(propagationState.getArguments()).isNotNull(); - assertThat(propagationState.getArguments().get(PROPAGATION_CONFIG_ARGUMENT)).isNotNull(); - assertThat(propagationState.getArguments().get(PROPAGATION_CONFIG_ARGUMENT).isEmpty()).isTrue(); + assertThat(propagationState.getArguments().get(PROPAGATION_CONFIG_ARGUMENT)).isEqualTo(propagationArgumentEntry); assertThat(propagationState.getArguments().get("state")).isNotNull().isEqualTo(singleValueArgumentEntry); assertThat(propagationState.getRequiredArguments()).isNull(); assertThat(propagationState.getReadinessStatus()).isNull(); diff --git a/dao/src/main/java/org/thingsboard/server/dao/exception/DataValidationException.java b/common/data/src/main/java/org/thingsboard/server/exception/DataValidationException.java similarity index 88% rename from dao/src/main/java/org/thingsboard/server/dao/exception/DataValidationException.java rename to common/data/src/main/java/org/thingsboard/server/exception/DataValidationException.java index 3fb1e0dc10..6db98312e7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/exception/DataValidationException.java +++ b/common/data/src/main/java/org/thingsboard/server/exception/DataValidationException.java @@ -13,12 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.exception; +package org.thingsboard.server.exception; public class DataValidationException extends RuntimeException { - private static final long serialVersionUID = 7659985660312721830L; - public DataValidationException(String message) { super(message); } @@ -26,4 +24,5 @@ public class DataValidationException extends RuntimeException { public DataValidationException(String message, Throwable cause) { super(message, cause); } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/exception/EntitiesLimitExceededException.java b/common/data/src/main/java/org/thingsboard/server/exception/EntitiesLimitExceededException.java similarity index 91% rename from dao/src/main/java/org/thingsboard/server/dao/exception/EntitiesLimitExceededException.java rename to common/data/src/main/java/org/thingsboard/server/exception/EntitiesLimitExceededException.java index b2907c9104..39b900cb98 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/exception/EntitiesLimitExceededException.java +++ b/common/data/src/main/java/org/thingsboard/server/exception/EntitiesLimitExceededException.java @@ -13,14 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.exception; +package org.thingsboard.server.exception; import lombok.Getter; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.TenantId; public class EntitiesLimitExceededException extends DataValidationException { - private static final long serialVersionUID = -9211462514373279196L; @Getter private final TenantId tenantId; @@ -36,4 +35,5 @@ public class EntitiesLimitExceededException extends DataValidationException { this.entityType = entityType; this.limit = limit; } + } diff --git a/common/data/src/main/java/org/thingsboard/server/exception/ThingsboardRuntimeException.java b/common/data/src/main/java/org/thingsboard/server/exception/ThingsboardRuntimeException.java new file mode 100644 index 0000000000..0a0b8ca718 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/exception/ThingsboardRuntimeException.java @@ -0,0 +1,51 @@ +/** + * Copyright © 2016-2025 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.exception; + +import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; + +public class ThingsboardRuntimeException extends RuntimeException { + + private ThingsboardErrorCode errorCode; + + public ThingsboardRuntimeException() { + super(); + } + + public ThingsboardRuntimeException(ThingsboardErrorCode errorCode) { + this.errorCode = errorCode; + } + + public ThingsboardRuntimeException(String message, ThingsboardErrorCode errorCode) { + super(message); + this.errorCode = errorCode; + } + + public ThingsboardRuntimeException(String message, Throwable cause, ThingsboardErrorCode errorCode) { + super(message, cause); + this.errorCode = errorCode; + } + + public ThingsboardRuntimeException(Throwable cause, ThingsboardErrorCode errorCode) { + super(cause); + this.errorCode = errorCode; + } + + public ThingsboardErrorCode getErrorCode() { + return errorCode; + } + +} diff --git a/common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeRpcClient.java b/common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeRpcClient.java index 423c59251b..ed0d5b603e 100644 --- a/common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeRpcClient.java +++ b/common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeRpcClient.java @@ -41,4 +41,5 @@ public interface EdgeRpcClient { void sendDownlinkResponseMsg(DownlinkResponseMsg downlinkResponseMsg); int getServerMaxInboundMessageSize(); + } diff --git a/common/proto/src/main/proto/queue.proto b/common/proto/src/main/proto/queue.proto index 9fb8528bce..3cbc84ba1a 100644 --- a/common/proto/src/main/proto/queue.proto +++ b/common/proto/src/main/proto/queue.proto @@ -935,6 +935,7 @@ message CalculatedFieldStateProto { int64 lastArgsUpdateTs = 7; int64 lastMetricsEvalTs = 8; repeated ArgumentIntervalProto aggregationArguments = 9; + repeated EntityIdProto propagationEntityIds = 10; } //Used to report session state to tb-Service and persist this state in the cache on the tb-Service level. diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java index 8e5f50da1d..517bc349a1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java @@ -64,7 +64,7 @@ import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.eventsourcing.ActionEntityEvent; import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.service.ConstraintValidator; import org.thingsboard.server.dao.tenant.TenantService; diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java index 86f3cfe130..6b985a5977 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java @@ -37,7 +37,7 @@ import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.entity.CachedVersionedEntityService; import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.resource.ImageService; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java b/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java index 7d8442fd3a..64cb66f6a8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java @@ -53,7 +53,7 @@ import org.thingsboard.server.dao.entity.EntityCountService; import org.thingsboard.server.dao.eventsourcing.ActionEntityEvent; import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.sql.JpaExecutorService; diff --git a/dao/src/main/java/org/thingsboard/server/dao/component/BaseComponentDescriptorService.java b/dao/src/main/java/org/thingsboard/server/dao/component/BaseComponentDescriptorService.java index 2c5d6fb3c5..7708e08f31 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/component/BaseComponentDescriptorService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/component/BaseComponentDescriptorService.java @@ -30,7 +30,7 @@ import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.plugin.ComponentDescriptor; import org.thingsboard.server.common.data.plugin.ComponentScope; import org.thingsboard.server.common.data.plugin.ComponentType; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.Validator; diff --git a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java index 28e39ae4e7..d6811b4812 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java @@ -45,7 +45,7 @@ import org.thingsboard.server.dao.entity.AbstractCachedEntityService; import org.thingsboard.server.dao.entity.EntityCountService; import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java index 71e912c215..cc2579d645 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java @@ -50,7 +50,7 @@ import org.thingsboard.server.dao.entity.EntityCountService; import org.thingsboard.server.dao.eventsourcing.ActionEntityEvent; import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.resource.ImageService; import org.thingsboard.server.dao.resource.ResourceService; import org.thingsboard.server.dao.service.DataValidator; diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java index 3423a74543..ceb35109c2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java @@ -43,9 +43,9 @@ import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.dao.entity.AbstractCachedEntityService; import org.thingsboard.server.dao.eventsourcing.ActionEntityEvent; -import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException; import org.thingsboard.server.dao.service.validator.DeviceCredentialsDataValidator; +import org.thingsboard.server.exception.DataValidationException; import java.util.Objects; diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java index 612be215e5..e377adf9ff 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java @@ -46,11 +46,11 @@ import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.dao.entity.CachedVersionedEntityService; import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; -import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.resource.ImageService; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.Validator; import org.thingsboard.server.dao.service.validator.DeviceProfileDataValidator; +import org.thingsboard.server.exception.DataValidationException; import java.io.ByteArrayInputStream; import java.security.cert.Certificate; diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index 5af2198911..fc8c7918a6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -80,7 +80,7 @@ import org.thingsboard.server.dao.event.EventService; import org.thingsboard.server.dao.eventsourcing.ActionEntityEvent; import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.validator.DeviceDataValidator; diff --git a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java index 708876c393..43a5b9d36b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java @@ -66,7 +66,7 @@ import org.thingsboard.server.dao.entity.EntityCountService; import org.thingsboard.server.dao.eventsourcing.ActionEntityEvent; import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.service.DataValidator; diff --git a/dao/src/main/java/org/thingsboard/server/dao/edge/PostgresEdgeEventService.java b/dao/src/main/java/org/thingsboard/server/dao/edge/PostgresEdgeEventService.java index ecf1376a73..e38b115e88 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/edge/PostgresEdgeEventService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/edge/PostgresEdgeEventService.java @@ -68,7 +68,8 @@ public class PostgresEdgeEventService extends BaseEdgeEventService { Futures.addCallback(saveFuture, new FutureCallback<>() { @Override public void onSuccess(Void result) { - statsCounterService.ifPresent(statsCounterService -> statsCounterService.recordEvent(EdgeStatsKey.DOWNLINK_MSGS_ADDED, edgeEvent.getTenantId(), edgeEvent.getEdgeId(), 1)); + statsCounterService.ifPresent(statsCounterService -> + statsCounterService.recordEvent(EdgeStatsKey.DOWNLINK_MSGS_ADDED, edgeEvent.getTenantId(), edgeEvent.getEdgeId(), 1)); eventPublisher.publishEvent(SaveEntityEvent.builder() .tenantId(edgeEvent.getTenantId()) .entityId(edgeEvent.getEdgeId()) diff --git a/dao/src/main/java/org/thingsboard/server/dao/edge/stats/EdgeStatsCounterService.java b/dao/src/main/java/org/thingsboard/server/dao/edge/stats/EdgeStatsCounterService.java index 16111cf514..ea8b85a287 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/edge/stats/EdgeStatsCounterService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/edge/stats/EdgeStatsCounterService.java @@ -24,13 +24,13 @@ import org.thingsboard.server.common.data.id.TenantId; import java.util.concurrent.ConcurrentHashMap; -@ConditionalOnProperty(prefix = "edges.stats", name = "enabled", havingValue = "true", matchIfMissing = false) +@ConditionalOnProperty(prefix = "edges.stats", name = "enabled", havingValue = "true") @Service @Slf4j @Getter public class EdgeStatsCounterService { - private final ConcurrentHashMap counterByEdge = new ConcurrentHashMap<>(); + private final ConcurrentHashMap msgCountersByEdge = new ConcurrentHashMap<>(); public void recordEvent(EdgeStatsKey type, TenantId tenantId, EdgeId edgeId, long value) { MsgCounters counters = getOrCreateCounters(tenantId, edgeId); @@ -39,19 +39,16 @@ public class EdgeStatsCounterService { case DOWNLINK_MSGS_PUSHED -> counters.getMsgsPushed().addAndGet(value); case DOWNLINK_MSGS_PERMANENTLY_FAILED -> counters.getMsgsPermanentlyFailed().addAndGet(value); case DOWNLINK_MSGS_TMP_FAILED -> counters.getMsgsTmpFailed().addAndGet(value); + case DOWNLINK_MSGS_LAG -> counters.getMsgsLag().set(value); } } - public void setDownlinkMsgsLag(TenantId tenantId, EdgeId edgeId, long value) { - getOrCreateCounters(tenantId, edgeId).getMsgsLag().set(value); + public MsgCounters getOrCreateCounters(TenantId tenantId, EdgeId edgeId) { + return msgCountersByEdge.computeIfAbsent(edgeId, id -> new MsgCounters(tenantId)); } public void clear(EdgeId edgeId) { - counterByEdge.remove(edgeId); - } - - public MsgCounters getOrCreateCounters(TenantId tenantId, EdgeId edgeId) { - return counterByEdge.computeIfAbsent(edgeId, id -> new MsgCounters(tenantId)); + msgCountersByEdge.remove(edgeId); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java b/dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java index 5e0a0df5ad..2a695d5724 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java @@ -44,7 +44,7 @@ import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.cf.CalculatedFieldService; import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.entityview.EntityViewService; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.housekeeper.CleanUpService; import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; diff --git a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java index e7edc93343..11518aaaae 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java @@ -51,7 +51,7 @@ import org.thingsboard.server.dao.entity.CachedVersionedEntityService; import org.thingsboard.server.dao.eventsourcing.ActionEntityEvent; import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.validator.EntityViewDataValidator; import org.thingsboard.server.dao.sql.JpaExecutorService; diff --git a/dao/src/main/java/org/thingsboard/server/dao/exception/DeviceCredentialsValidationException.java b/dao/src/main/java/org/thingsboard/server/dao/exception/DeviceCredentialsValidationException.java index b2cd2cc7bf..3dcbfe2806 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/exception/DeviceCredentialsValidationException.java +++ b/dao/src/main/java/org/thingsboard/server/dao/exception/DeviceCredentialsValidationException.java @@ -15,8 +15,12 @@ */ package org.thingsboard.server.dao.exception; +import org.thingsboard.server.exception.DataValidationException; + public class DeviceCredentialsValidationException extends DataValidationException { + public DeviceCredentialsValidationException(String message) { super(message); } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateServiceImpl.java index 21dd339c8a..03cc68876a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateServiceImpl.java @@ -23,7 +23,7 @@ import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationTemplateId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate; import org.thingsboard.server.dao.entity.AbstractEntityService; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import java.util.List; diff --git a/dao/src/main/java/org/thingsboard/server/dao/ota/BaseOtaPackageService.java b/dao/src/main/java/org/thingsboard/server/dao/ota/BaseOtaPackageService.java index 16894fe56c..e20b6b872f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/ota/BaseOtaPackageService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/ota/BaseOtaPackageService.java @@ -41,7 +41,7 @@ import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.entity.AbstractCachedEntityService; import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; diff --git a/dao/src/main/java/org/thingsboard/server/dao/queue/BaseQueueService.java b/dao/src/main/java/org/thingsboard/server/dao/queue/BaseQueueService.java index 248326a722..991193aa78 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/queue/BaseQueueService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/queue/BaseQueueService.java @@ -34,7 +34,7 @@ import org.thingsboard.server.common.data.queue.Queue; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.Validator; diff --git a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java index 90dd63f7da..9e212c6d96 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java @@ -52,7 +52,7 @@ import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.eventsourcing.RelationActionEvent; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.service.ConstraintValidator; import org.thingsboard.server.dao.sql.JpaExecutorService; import org.thingsboard.server.dao.sql.relation.JpaRelationQueryExecutorService; diff --git a/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java b/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java index 2177d493e3..b18580544a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java @@ -58,7 +58,7 @@ import org.thingsboard.server.dao.dashboard.DashboardInfoDao; import org.thingsboard.server.dao.entity.AbstractCachedEntityService; import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.rule.RuleChainDao; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.Validator; diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java index ea93df5665..d225be8015 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java @@ -63,11 +63,11 @@ import org.thingsboard.server.dao.entity.EntityCountService; import org.thingsboard.server.dao.eventsourcing.ActionEntityEvent; import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; -import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.Validator; import org.thingsboard.server.dao.service.validator.RuleChainDataValidator; +import org.thingsboard.server.exception.DataValidationException; import java.util.ArrayList; import java.util.Collection; diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleNodeStateService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleNodeStateService.java index e534596e34..2d7b70a39f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleNodeStateService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleNodeStateService.java @@ -26,7 +26,7 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.rule.RuleNodeState; import org.thingsboard.server.dao.entity.AbstractEntityService; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; @Service @Slf4j diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/ConstraintValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/ConstraintValidator.java index 0fd55bb494..e354638b0d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/ConstraintValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/ConstraintValidator.java @@ -35,7 +35,7 @@ import org.thingsboard.server.common.data.validation.NoNullChar; import org.thingsboard.server.common.data.validation.NoXss; import org.thingsboard.server.common.data.validation.RateLimit; import org.thingsboard.server.common.data.validation.ValidJsonSchema; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import java.util.Collection; import java.util.Set; diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java index 89ab237ed3..4376aa5c64 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java @@ -16,6 +16,8 @@ package org.thingsboard.server.dao.service; import com.fasterxml.jackson.databind.JsonNode; +import org.thingsboard.server.exception.DataValidationException; +import org.thingsboard.server.exception.EntitiesLimitExceededException; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.FileUtils; import org.springframework.beans.factory.annotation.Autowired; @@ -27,8 +29,6 @@ import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.TenantEntityWithDataDao; -import org.thingsboard.server.dao.exception.DataValidationException; -import org.thingsboard.server.dao.exception.EntitiesLimitExceededException; import org.thingsboard.server.dao.usagerecord.ApiLimitService; import java.util.HashSet; @@ -51,7 +51,8 @@ public abstract class DataValidator> { private static final String NAME = "name"; private static final String TOPIC = "topic"; - @Autowired @Lazy + @Autowired + @Lazy private ApiLimitService apiLimitService; // Returns old instance of the same object that is fetched during validation. diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AbstractHasOtaPackageValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AbstractHasOtaPackageValidator.java index eb5fa340e0..5e7382c0ce 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AbstractHasOtaPackageValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AbstractHasOtaPackageValidator.java @@ -23,7 +23,7 @@ import org.thingsboard.server.common.data.OtaPackage; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.ota.OtaPackageType; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.service.DataValidator; diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AdminSettingsDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AdminSettingsDataValidator.java index 779c52c222..5b158dd998 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AdminSettingsDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AdminSettingsDataValidator.java @@ -19,7 +19,7 @@ import lombok.AllArgsConstructor; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.settings.AdminSettingsService; diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AiModelDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AiModelDataValidator.java index fdccf2955f..8a4680b70d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AiModelDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AiModelDataValidator.java @@ -20,7 +20,7 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.ai.AiModel; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.ai.AiModelDao; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.tenant.TenantService; diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AlarmCommentDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AlarmCommentDataValidator.java index e8192cb7b4..084b672efa 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AlarmCommentDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AlarmCommentDataValidator.java @@ -19,7 +19,7 @@ import lombok.AllArgsConstructor; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.alarm.AlarmComment; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; @Component diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AlarmDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AlarmDataValidator.java index b6d6ba2537..1e37ed4b2f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AlarmDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AlarmDataValidator.java @@ -19,7 +19,7 @@ import lombok.AllArgsConstructor; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.tenant.TenantService; diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/ApiKeyDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/ApiKeyDataValidator.java index e539c1e721..7b2ebe9d40 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/ApiKeyDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/ApiKeyDataValidator.java @@ -19,7 +19,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.pat.ApiKey; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.pat.ApiKeyDao; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.tenant.TenantService; diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/ApiUsageDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/ApiUsageDataValidator.java index 1e03f5793c..30161fe978 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/ApiUsageDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/ApiUsageDataValidator.java @@ -21,7 +21,7 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.tenant.TenantService; diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetDataValidator.java index 8f3fe62d03..71d1b2d960 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetDataValidator.java @@ -25,7 +25,7 @@ import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.asset.AssetDao; import org.thingsboard.server.dao.customer.CustomerDao; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.tenant.TenantService; diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetProfileDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetProfileDataValidator.java index 83211a3b86..a7eece823a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetProfileDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetProfileDataValidator.java @@ -27,7 +27,7 @@ import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.dao.asset.AssetProfileDao; import org.thingsboard.server.dao.asset.AssetProfileService; import org.thingsboard.server.dao.dashboard.DashboardService; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.service.DataValidator; diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AuditLogDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AuditLogDataValidator.java index 14cc77f7a1..f98e38f967 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AuditLogDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AuditLogDataValidator.java @@ -18,7 +18,7 @@ package org.thingsboard.server.dao.service.validator; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.audit.AuditLog; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; @Component diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/BaseOtaPackageDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/BaseOtaPackageDataValidator.java index 761a99c621..e3d4c9bb90 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/BaseOtaPackageDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/BaseOtaPackageDataValidator.java @@ -23,9 +23,9 @@ import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.OtaPackageInfo; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.dao.device.DeviceProfileDao; -import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.tenant.TenantService; +import org.thingsboard.server.exception.DataValidationException; import java.util.Objects; 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 6c333f36ad..7926d8c8ab 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 @@ -27,7 +27,7 @@ import org.thingsboard.server.common.data.cf.configuration.aggregation.single.En import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.dao.cf.CalculatedFieldDao; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.usagerecord.ApiLimitService; diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/ClientRegistrationTemplateDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/ClientRegistrationTemplateDataValidator.java index 830c602ccb..1701f064dc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/ClientRegistrationTemplateDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/ClientRegistrationTemplateDataValidator.java @@ -19,7 +19,7 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; @Component diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/ComponentDescriptorDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/ComponentDescriptorDataValidator.java index f834f6bce5..8275a7e4b6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/ComponentDescriptorDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/ComponentDescriptorDataValidator.java @@ -19,7 +19,7 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.plugin.ComponentDescriptor; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; @Component diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/CustomerDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/CustomerDataValidator.java index c6e547adbf..ba80bda39f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/CustomerDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/CustomerDataValidator.java @@ -23,7 +23,7 @@ import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.customer.CustomerDao; import org.thingsboard.server.dao.customer.CustomerServiceImpl; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.tenant.TenantService; diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DashboardDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DashboardDataValidator.java index aa205e9646..22f27cd260 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DashboardDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DashboardDataValidator.java @@ -20,7 +20,7 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.tenant.TenantService; diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceDataValidator.java index d61f9d455d..de1646e44b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceDataValidator.java @@ -25,7 +25,7 @@ import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.customer.CustomerDao; import org.thingsboard.server.dao.device.DeviceDao; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.tenant.TenantService; import java.util.Optional; diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java index 401b199598..9fac76704c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java @@ -52,11 +52,11 @@ import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.device.DeviceDao; import org.thingsboard.server.dao.device.DeviceProfileDao; import org.thingsboard.server.dao.device.DeviceProfileService; -import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException; import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.tenant.TenantService; +import org.thingsboard.server.exception.DataValidationException; import java.io.FileInputStream; import java.security.KeyStore; @@ -341,7 +341,7 @@ public class DeviceProfileDataValidator extends AbstractHasOtaPackageValidator end - start); } -} \ No newline at end of file +} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/CustomerServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/CustomerServiceTest.java index e69685f429..b2432ccc7c 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/CustomerServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/CustomerServiceTest.java @@ -44,7 +44,7 @@ import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.cf.CalculatedFieldService; import org.thingsboard.server.dao.customer.CustomerService; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import java.util.ArrayList; import java.util.List; diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/DashboardServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/DashboardServiceTest.java index 6c81d31e57..333e3ce4ff 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/DashboardServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/DashboardServiceTest.java @@ -35,7 +35,7 @@ import org.thingsboard.server.common.data.page.SortOrder; import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.edge.EdgeService; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import java.io.IOException; import java.util.ArrayList; diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/DataValidatorTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/DataValidatorTest.java index c8ad03f79c..a59d5dc43f 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/DataValidatorTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/DataValidatorTest.java @@ -20,7 +20,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.spy; diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/DeviceCredentialsServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/DeviceCredentialsServiceTest.java index d5c969b6f3..641b8465fd 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/DeviceCredentialsServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/DeviceCredentialsServiceTest.java @@ -31,7 +31,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.eventsourcing.ActionEntityEvent; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; @DaoSqlTest public class DeviceCredentialsServiceTest extends AbstractServiceTest { diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/DeviceProfileServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/DeviceProfileServiceTest.java index c89902befd..cce711e061 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/DeviceProfileServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/DeviceProfileServiceTest.java @@ -36,7 +36,7 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceService; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.ota.OtaPackageService; import java.nio.ByteBuffer; diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/DeviceServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/DeviceServiceTest.java index 5157bb1b63..ed791ec27e 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/DeviceServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/DeviceServiceTest.java @@ -68,7 +68,7 @@ import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceService; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException; import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.service.validator.DeviceCredentialsDataValidator; diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/EdgeServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/EdgeServiceTest.java index 32d0c2cb84..8f4977ad8c 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/EdgeServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/EdgeServiceTest.java @@ -40,7 +40,7 @@ import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.edge.EdgeService; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantProfileService; diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/OAuth2ConfigTemplateServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/OAuth2ConfigTemplateServiceTest.java index 460a1424fc..0451b740aa 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/OAuth2ConfigTemplateServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/OAuth2ConfigTemplateServiceTest.java @@ -26,7 +26,7 @@ import org.thingsboard.server.common.data.oauth2.MapperType; import org.thingsboard.server.common.data.oauth2.OAuth2BasicMapperConfig; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate; import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService; import java.util.Arrays; diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/OtaPackageServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/OtaPackageServiceTest.java index 70f2ff119d..48a8792452 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/OtaPackageServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/OtaPackageServiceTest.java @@ -36,7 +36,7 @@ import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceService; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.tenant.TenantProfileService; diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/QueueServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/QueueServiceTest.java index 3a92a817f7..a65b36a657 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/QueueServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/QueueServiceTest.java @@ -35,7 +35,7 @@ import org.thingsboard.server.common.data.queue.SubmitStrategy; import org.thingsboard.server.common.data.queue.SubmitStrategyType; import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.data.tenant.profile.TenantProfileQueueConfiguration; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.tenant.TenantProfileService; diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/QueueStatsServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/QueueStatsServiceTest.java index 685efbe2f6..9886841c69 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/QueueStatsServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/QueueStatsServiceTest.java @@ -27,7 +27,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.queue.QueueStats; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.queue.QueueStatsService; import static org.assertj.core.api.Assertions.assertThat; diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/RelationServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/RelationServiceTest.java index e3827d6a6e..a0f27be4e2 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/RelationServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/RelationServiceTest.java @@ -35,7 +35,7 @@ import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter; import org.thingsboard.server.common.data.relation.RelationPathLevel; import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.relation.RelationsSearchParameters; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/RuleChainServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/RuleChainServiceTest.java index 11fa38dc19..fc26ab96cc 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/RuleChainServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/RuleChainServiceTest.java @@ -34,7 +34,7 @@ import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.dao.edge.EdgeService; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.rule.RuleChainService; diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/TenantProfileServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/TenantProfileServiceTest.java index 5887f6868e..51a2f6a09d 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/TenantProfileServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/TenantProfileServiceTest.java @@ -38,7 +38,7 @@ import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileCon import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.data.tenant.profile.TenantProfileQueueConfiguration; import org.thingsboard.server.common.util.ProtoUtils; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.gen.transport.TransportProtos; diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/TenantServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/TenantServiceTest.java index def4644b91..cb337d9c62 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/TenantServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/TenantServiceTest.java @@ -62,7 +62,7 @@ import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.entityview.EntityViewService; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.resource.ResourceService; import org.thingsboard.server.dao.rpc.RpcService; diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/UserServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/UserServiceTest.java index ad87e73f79..25d01c40ad 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/UserServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/UserServiceTest.java @@ -33,7 +33,7 @@ import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.common.data.settings.UserSettings; import org.thingsboard.server.dao.customer.CustomerService; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.user.UserService; import java.util.ArrayList; diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/WidgetTypeServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/WidgetTypeServiceTest.java index 0e6c7beadb..aef0a17a38 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/WidgetTypeServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/WidgetTypeServiceTest.java @@ -27,7 +27,7 @@ import org.thingsboard.server.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.widget.WidgetType; import org.thingsboard.server.common.data.widget.WidgetTypeDetails; import org.thingsboard.server.common.data.widget.WidgetsBundle; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.widget.WidgetTypeService; import org.thingsboard.server.dao.widget.WidgetsBundleService; diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/WidgetsBundleServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/WidgetsBundleServiceTest.java index 0165d3d164..eff2a15f9c 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/WidgetsBundleServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/WidgetsBundleServiceTest.java @@ -25,7 +25,7 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.common.data.widget.WidgetsBundleFilter; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.widget.WidgetsBundleService; diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/validator/AssetDataValidatorTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/validator/AssetDataValidatorTest.java index 82659ead60..c3db7a251b 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/validator/AssetDataValidatorTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/validator/AssetDataValidatorTest.java @@ -27,7 +27,7 @@ import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.asset.AssetDao; import org.thingsboard.server.dao.customer.CustomerDao; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.tenant.TenantService; import java.util.UUID; diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/validator/CalculatedFieldDataValidatorTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/validator/CalculatedFieldDataValidatorTest.java index 0d52f999de..38f097616d 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/validator/CalculatedFieldDataValidatorTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/validator/CalculatedFieldDataValidatorTest.java @@ -24,7 +24,7 @@ import org.thingsboard.server.common.data.cf.CalculatedFieldType; import org.thingsboard.server.common.data.id.CalculatedFieldId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.cf.CalculatedFieldDao; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.usagerecord.DefaultApiLimitService; import java.util.UUID; diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/validator/DeviceDataValidatorTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/validator/DeviceDataValidatorTest.java index 687c3e67ac..12b2e26746 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/validator/DeviceDataValidatorTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/validator/DeviceDataValidatorTest.java @@ -27,7 +27,7 @@ import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.customer.CustomerDao; import org.thingsboard.server.dao.device.DeviceDao; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.tenant.TenantService; import java.util.UUID; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractCustomerActionNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractCustomerActionNode.java index ad9ce41ea3..c6e67bd65c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractCustomerActionNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractCustomerActionNode.java @@ -31,7 +31,7 @@ import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.util.TbPair; import org.thingsboard.server.common.msg.TbMsg; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import java.util.EnumSet; import java.util.NoSuchElementException; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java index 83b777d153..17ab7e4022 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java @@ -35,7 +35,7 @@ import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.util.TbPair; import org.thingsboard.server.common.msg.TbMsg; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import java.util.EnumSet; import java.util.NoSuchElementException; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/ai/TbAiNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/ai/TbAiNode.java index a9ebb7ecb7..4908ca942a 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/ai/TbAiNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/ai/TbAiNode.java @@ -54,7 +54,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.msg.TbMsg; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.resource.TbResourceDataCache; import java.nio.charset.StandardCharsets; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/lambda/TbAwsLambdaNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/lambda/TbAwsLambdaNode.java index c4e448e2ee..e5bdf734bd 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/lambda/TbAwsLambdaNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/lambda/TbAwsLambdaNode.java @@ -35,7 +35,7 @@ import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import java.nio.ByteBuffer; import java.util.concurrent.TimeUnit; diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/ai/TbAiNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/ai/TbAiNodeTest.java index a786cdd536..cadaacda68 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/ai/TbAiNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/ai/TbAiNodeTest.java @@ -67,7 +67,7 @@ import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.dao.ai.AiModelService; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.resource.ResourceService; import org.thingsboard.server.dao.resource.TbResourceDataCache; diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeTest.java index 941dfb92d7..194d498d9a 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeTest.java @@ -46,7 +46,7 @@ import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.dao.attributes.AttributesService; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.service.ConstraintValidator; import java.util.List; diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNodeTest.java index 1141ff09d1..4af1234ded 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNodeTest.java @@ -49,7 +49,7 @@ import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileCon import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; -import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.dao.service.ConstraintValidator; import java.util.ArrayList; diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/output/calculated-field-output.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/output/calculated-field-output.component.ts index 149b5aa3b9..f1e8646a6c 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/output/calculated-field-output.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/output/calculated-field-output.component.ts @@ -122,6 +122,10 @@ export class CalculatedFieldOutputComponent implements ControlValueAccessor, Val this.updatedStrategy(); }); + this.outputForm.get('strategy.saveTimeSeries').valueChanges + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(value => this.updateTimeSeriesTtl(value)); + merge( this.outputForm.get('strategy.type').valueChanges, this.outputForm.get('strategy.useCustomTtl').valueChanges @@ -181,6 +185,7 @@ export class CalculatedFieldOutputComponent implements ControlValueAccessor, Val this.updatedFormWithMode(); this.toggleScopeByOutputType(this.outputForm.get('type').value); this.updatedStrategy(); + this.updateTimeSeriesTtl(this.outputForm.get('strategy.saveTimeSeries').value); } } @@ -245,4 +250,13 @@ export class CalculatedFieldOutputComponent implements ControlValueAccessor, Val } } } + + private updateTimeSeriesTtl(value: boolean) { + if (value) { + this.outputForm.get('strategy.useCustomTtl').enable({emitEvent: true}); + } else { + this.outputForm.get('strategy.useCustomTtl').disable({emitEvent: false}); + this.outputForm.get('strategy.ttl').disable({emitEvent: false}); + } + } } diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/propagation-configuration/propagation-configuration.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/components/propagation-configuration/propagation-configuration.component.html index 6dc5702735..8abbfb2056 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/propagation-configuration/propagation-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/propagation-configuration/propagation-configuration.component.html @@ -55,6 +55,7 @@ [tenantId]="tenantId" [entityName]="entityName" [ownerId]="ownerId" + [watchKeyChange]="true" [isScript]="this.propagateConfiguration.get('applyExpressionToResolvedArguments').value"/>
@@ -77,7 +78,7 @@
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.component.scss index c8801c4fc0..e81484f04d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.component.scss @@ -106,6 +106,9 @@ $backgroundColorDisabled: var(--tb-slider-background-color-disabled, #D5D7E5); height: 6px; min-height: 6px; min-width: 0; + .mdc-slider__input{ + top: -21px; + } &.mdc-slider--disabled { opacity: 1; } diff --git a/ui-ngx/src/app/shared/components/time/datapoints-limit.component.scss b/ui-ngx/src/app/shared/components/time/datapoints-limit.component.scss index 7d8ae18d11..6900bb4960 100644 --- a/ui-ngx/src/app/shared/components/time/datapoints-limit.component.scss +++ b/ui-ngx/src/app/shared/components/time/datapoints-limit.component.scss @@ -29,6 +29,9 @@ .mdc-slider { height: 40px; + .mdc-slider__input { + top: 0; + } ::ng-deep { .mdc-slider__thumb { left: -20px; diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index a480c86fdb..960d3d3857 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1121,7 +1121,6 @@ "datasource": "Datasource", "add-argument": "Add argument", "test-script-function": "Test script function", - "test-expression-function": "Test expression function", "no-arguments": "At least one argument is required.", "argument-settings": "Argument settings", "argument-current": "Current entity", @@ -1316,8 +1315,8 @@ "offset-value-required": "Offset value is required", "offset-value-min": "Offset value must be a positive integer", "offset-value-max": "Offset value should be less than the aggregate interval value", - "wait-delay": "Wait for delayed telemetry", - "wait-delay-hint": "Waits for delayed telemetry after the interval ends.", + "wait-delay": "Apply await timeout for delayed telemetry", + "wait-delay-hint": "Defines how long to wait for delayed telemetry after the interval ends. If such telemetry arrives, the result for that interval will be recalculated.", "duration": "Duration", "duration-required": "Duration is required", "duration-min": "Duration should be at least 1 minute",