diff --git a/README.md b/README.md index c9e6e17fc2..6bce2db844 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,6 @@ Collect and Visualize your IoT data in minutes by following this [guide](https:/ ## Support - - [Community chat](https://gitter.im/thingsboard/chat) - [Q&A forum](https://groups.google.com/forum/#!forum/thingsboard) - [Stackoverflow](http://stackoverflow.com/questions/tagged/thingsboard) diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java index f6f2aef6f1..1c18a31f68 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -570,6 +570,10 @@ public class ActorSystemContext { @Getter private String rpcSubmitStrategy; + @Value("${actors.rpc.close_session_on_rpc_delivery_timeout:false}") + @Getter + private boolean closeTransportSessionOnRpcDeliveryTimeout; + @Value("${actors.rpc.response_timeout_ms:30000}") @Getter private long rpcResponseTimeout; diff --git a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java index 3e8cd9592f..3dca3417b4 100644 --- a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java @@ -132,6 +132,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso private final boolean rpcSequential; private final RpcSubmitStrategy rpcSubmitStrategy; private final ScheduledExecutorService scheduler; + private final boolean closeTransportSessionOnRpcDeliveryTimeout; private int rpcSeq = 0; private String deviceName; @@ -145,6 +146,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso this.tenantId = tenantId; this.deviceId = deviceId; this.rpcSubmitStrategy = RpcSubmitStrategy.parse(systemContext.getRpcSubmitStrategy()); + this.closeTransportSessionOnRpcDeliveryTimeout = systemContext.isCloseTransportSessionOnRpcDeliveryTimeout(); this.rpcSequential = !rpcSubmitStrategy.equals(RpcSubmitStrategy.BURST); this.attributeSubscriptions = new HashMap<>(); this.rpcSubscriptions = new HashMap<>(); @@ -223,7 +225,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso log.error("[{}][{}][{}] Failed to save RPC request to edge queue {}", tenantId, deviceId, edgeId.getId(), request, e); } } else if (isSendNewRpcAvailable()) { - sent = rpcSubscriptions.size() > 0; + sent = !rpcSubscriptions.isEmpty(); Set syncSessionSet = new HashSet<>(); rpcSubscriptions.forEach((sessionId, sessionInfo) -> { log.debug("[{}][{}][{}][{}] send RPC request to transport ...", deviceId, sessionId, rpcId, requestId); @@ -255,8 +257,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso private boolean isSendNewRpcAvailable() { return switch (rpcSubmitStrategy) { case SEQUENTIAL_ON_ACK_FROM_DEVICE -> toDeviceRpcPendingMap.values().stream().filter(md -> !md.isDelivered()).findAny().isEmpty(); - case SEQUENTIAL_ON_RESPONSE_FROM_DEVICE -> - toDeviceRpcPendingMap.values().stream().filter(ToDeviceRpcRequestMetadata::isDelivered).findAny().isEmpty(); + case SEQUENTIAL_ON_RESPONSE_FROM_DEVICE -> toDeviceRpcPendingMap.isEmpty(); default -> true; }; } @@ -598,7 +599,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso } void processAttributesUpdate(DeviceAttributesEventNotificationMsg msg) { - if (attributeSubscriptions.size() > 0) { + if (!attributeSubscriptions.isEmpty()) { boolean hasNotificationData = false; AttributeUpdateNotificationMsg.Builder notification = AttributeUpdateNotificationMsg.newBuilder(); if (msg.isDeleted()) { @@ -613,7 +614,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso } else { if (DataConstants.SHARED_SCOPE.equals(msg.getScope())) { List attributes = new ArrayList<>(msg.getValues()); - if (attributes.size() > 0) { + if (!attributes.isEmpty()) { List sharedUpdated = msg.getValues().stream().map(t -> KvProtoUtil.toTsKvProto(t.getLastUpdateTs(), t)) .collect(Collectors.toList()); if (!sharedUpdated.isEmpty()) { @@ -705,10 +706,16 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso maxRpcRetries = maxRpcRetries == null ? systemContext.getMaxRpcRetries() : Math.min(maxRpcRetries, systemContext.getMaxRpcRetries()); if (maxRpcRetries <= md.getRetries()) { - toDeviceRpcPendingMap.remove(requestId); - status = RpcStatus.FAILED; - response = JacksonUtil.newObjectNode().put("error", "There was a Timeout and all retry " + - "attempts have been exhausted. Retry attempts set: " + maxRpcRetries); + if (closeTransportSessionOnRpcDeliveryTimeout) { + md.setRetries(0); + status = RpcStatus.QUEUED; + notifyTransportAboutSessionsCloseAndDumpSessions(TransportSessionCloseReason.RPC_DELIVERY_TIMEOUT); + } else { + toDeviceRpcPendingMap.remove(requestId); + status = RpcStatus.FAILED; + response = JacksonUtil.newObjectNode().put("error", "There was a Timeout and all retry " + + "attempts have been exhausted. Retry attempts set: " + maxRpcRetries); + } } else { md.setRetries(md.getRetries() + 1); } @@ -843,28 +850,30 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso void processCredentialsUpdate(TbActorMsg msg) { if (((DeviceCredentialsUpdateNotificationMsg) msg).getDeviceCredentials().getCredentialsType() == DeviceCredentialsType.LWM2M_CREDENTIALS) { - sessions.forEach((k, v) -> { - notifyTransportAboutDeviceCredentialsUpdate(k, v, ((DeviceCredentialsUpdateNotificationMsg) msg).getDeviceCredentials()); - }); + sessions.forEach((k, v) -> + notifyTransportAboutDeviceCredentialsUpdate(k, v, ((DeviceCredentialsUpdateNotificationMsg) msg).getDeviceCredentials())); } else { - sessions.forEach((sessionId, sessionMd) -> notifyTransportAboutClosedSession(sessionId, sessionMd, "device credentials updated!", SessionCloseReason.CREDENTIALS_UPDATED)); - attributeSubscriptions.clear(); - rpcSubscriptions.clear(); - dumpSessions(); - + notifyTransportAboutSessionsCloseAndDumpSessions(TransportSessionCloseReason.CREDENTIALS_UPDATED); } } + private void notifyTransportAboutSessionsCloseAndDumpSessions(TransportSessionCloseReason transportSessionCloseReason) { + sessions.forEach((sessionId, sessionMd) -> notifyTransportAboutClosedSession(sessionId, sessionMd, transportSessionCloseReason)); + attributeSubscriptions.clear(); + rpcSubscriptions.clear(); + dumpSessions(); + } + private void notifyTransportAboutClosedSessionMaxSessionsLimit(UUID sessionId, SessionInfoMetaData sessionMd) { - log.debug("remove eldest session (max concurrent sessions limit reached per device) sessionId: [{}] sessionMd: [{}]", sessionId, sessionMd); - notifyTransportAboutClosedSession(sessionId, sessionMd, "max concurrent sessions limit reached per device!", SessionCloseReason.MAX_CONCURRENT_SESSIONS_LIMIT_REACHED); + notifyTransportAboutClosedSession(sessionId, sessionMd, TransportSessionCloseReason.MAX_CONCURRENT_SESSIONS_LIMIT_REACHED); } - private void notifyTransportAboutClosedSession(UUID sessionId, SessionInfoMetaData sessionMd, String message, SessionCloseReason reason) { + private void notifyTransportAboutClosedSession(UUID sessionId, SessionInfoMetaData sessionMd, TransportSessionCloseReason transportSessionCloseReason) { + log.debug("{} sessionId: [{}] sessionMd: [{}]", transportSessionCloseReason.getLogMessage(), sessionId, sessionMd); SessionCloseNotificationProto sessionCloseNotificationProto = SessionCloseNotificationProto .newBuilder() - .setMessage(message) - .setReason(reason) + .setMessage(transportSessionCloseReason.getNotificationMessage()) + .setReason(SessionCloseReason.forNumber(transportSessionCloseReason.getProtoNumber())) .build(); ToTransportMsg msg = ToTransportMsg.newBuilder() .setSessionIdMSB(sessionId.getMostSignificantBits()) @@ -1048,7 +1057,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso attributeSubscriptions.remove(id); if (session != null) { removed++; - notifyTransportAboutClosedSession(id, session, SESSION_TIMEOUT_MESSAGE, SessionCloseReason.SESSION_TIMEOUT); + notifyTransportAboutClosedSession(id, session, TransportSessionCloseReason.SESSION_TIMEOUT); } } if (removed != 0) { diff --git a/application/src/main/java/org/thingsboard/server/actors/device/TransportSessionCloseReason.java b/application/src/main/java/org/thingsboard/server/actors/device/TransportSessionCloseReason.java new file mode 100644 index 0000000000..ce8c936515 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/device/TransportSessionCloseReason.java @@ -0,0 +1,39 @@ +/** + * Copyright © 2016-2024 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.actors.device; + +import lombok.Getter; + +@Getter +public enum TransportSessionCloseReason { + + UNKNOWN_REASON(0, "Unknown Reason.", "Session closed with unknown reason."), + CREDENTIALS_UPDATED(1, "device credentials updated!", "Close session due to device credentials update."), + MAX_CONCURRENT_SESSIONS_LIMIT_REACHED(2, "max concurrent sessions limit reached per device!", "Remove eldest session (max concurrent sessions limit reached per device)."), + SESSION_TIMEOUT(3, "session timeout!", "Close session due to session timeout."), + RPC_DELIVERY_TIMEOUT(4, "RPC delivery failed!", "Close session due to RPC delivery failure."); + + private final int protoNumber; + private final String notificationMessage; + private final String logMessage; + + TransportSessionCloseReason(int protoNumber, String notificationMessage, String logMessage) { + this.protoNumber = protoNumber; + this.notificationMessage = notificationMessage; + this.logMessage = logMessage; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index a62cb432da..d27bf40650 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -110,6 +110,7 @@ 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.gen.transport.TransportProtos; +import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.common.SimpleTbQueueCallback; import org.thingsboard.server.service.executors.PubSubRuleNodeExecutorProvider; import org.thingsboard.server.service.script.RuleNodeJsScriptEngine; @@ -173,8 +174,13 @@ class DefaultTbContext implements TbContext { @Override public void input(TbMsg msg, RuleChainId ruleChainId) { - msg.pushToStack(nodeCtx.getSelf().getRuleChainId(), nodeCtx.getSelf().getId()); - nodeCtx.getChainActor().tell(new RuleChainInputMsg(ruleChainId, msg)); + if (!msg.isValid()) { + return; + } + TbMsg tbMsg = msg.copyWithRuleChainId(ruleChainId); + tbMsg.pushToStack(nodeCtx.getSelf().getRuleChainId(), nodeCtx.getSelf().getId()); + TopicPartitionInfo tpi = mainCtx.resolve(ServiceType.TB_RULE_ENGINE, getQueueName(), getTenantId(), tbMsg.getOriginator()); + doEnqueue(tpi, tbMsg, new SimpleTbQueueCallback(md -> ack(msg), t -> tellFailure(msg, t))); } @Override @@ -210,14 +216,10 @@ class DefaultTbContext implements TbContext { } return; } - TransportProtos.ToRuleEngineMsg msg = TransportProtos.ToRuleEngineMsg.newBuilder() - .setTenantIdMSB(getTenantId().getId().getMostSignificantBits()) - .setTenantIdLSB(getTenantId().getId().getLeastSignificantBits()) - .setTbMsg(TbMsg.toByteString(tbMsg)).build(); if (nodeCtx.getSelf().isDebugMode()) { mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), tbMsg, "To Root Rule Chain"); } - mainCtx.getClusterService().pushMsgToRuleEngine(tpi, tbMsg.getId(), msg, new SimpleTbQueueCallback( + doEnqueue(tpi, tbMsg, new SimpleTbQueueCallback( metadata -> { if (onSuccess != null) { onSuccess.run(); @@ -232,6 +234,14 @@ class DefaultTbContext implements TbContext { })); } + private void doEnqueue(TopicPartitionInfo tpi, TbMsg tbMsg, TbQueueCallback callback) { + TransportProtos.ToRuleEngineMsg msg = TransportProtos.ToRuleEngineMsg.newBuilder() + .setTenantIdMSB(getTenantId().getId().getMostSignificantBits()) + .setTenantIdLSB(getTenantId().getId().getLeastSignificantBits()) + .setTbMsg(TbMsg.toByteString(tbMsg)).build(); + mainCtx.getClusterService().pushMsgToRuleEngine(tpi, tbMsg.getId(), msg, callback); + } + @Override public void enqueueForTellFailure(TbMsg tbMsg, String failureMessage) { TopicPartitionInfo tpi = resolvePartition(tbMsg); diff --git a/application/src/main/java/org/thingsboard/server/controller/MobileAppController.java b/application/src/main/java/org/thingsboard/server/controller/MobileAppController.java index 362539f6b6..0673ce1fdf 100644 --- a/application/src/main/java/org/thingsboard/server/controller/MobileAppController.java +++ b/application/src/main/java/org/thingsboard/server/controller/MobileAppController.java @@ -168,7 +168,7 @@ public class MobileAppController extends BaseController { .collect(Collectors.toList()); return JacksonUtil.toJsonNode(JacksonUtil.writeValueAsViewIgnoringNullFields(mobilePages, Views.Public.class)); } else { - return JacksonUtil.newArrayNode(); + return null; } } 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 f0f41e3083..ee50bbb4a3 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 @@ -17,16 +17,20 @@ package org.thingsboard.server.service.edge; import lombok.Data; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; import org.thingsboard.server.cache.limits.RateLimitService; import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor; +import org.thingsboard.server.dao.alarm.AlarmCommentService; +import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.asset.AssetProfileService; import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.dashboard.DashboardService; +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.domain.DomainService; @@ -36,8 +40,10 @@ import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.notification.NotificationRuleService; import org.thingsboard.server.dao.notification.NotificationTargetService; import org.thingsboard.server.dao.notification.NotificationTemplateService; +import org.thingsboard.server.dao.oauth2.OAuth2ClientService; import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.queue.QueueService; +import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.resource.ResourceService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.settings.AdminSettingsService; @@ -49,6 +55,8 @@ import org.thingsboard.server.dao.widget.WidgetsBundleService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.edge.rpc.EdgeEventStorageSettings; import org.thingsboard.server.service.edge.rpc.EdgeRpcService; +import org.thingsboard.server.service.edge.rpc.constructor.asset.AssetMsgConstructorFactory; +import org.thingsboard.server.service.edge.rpc.constructor.device.DeviceMsgConstructorFactory; import org.thingsboard.server.service.edge.rpc.constructor.edge.EdgeMsgConstructor; import org.thingsboard.server.service.edge.rpc.processor.alarm.AlarmEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.alarm.AlarmEdgeProcessorFactory; @@ -83,155 +91,172 @@ import org.thingsboard.server.service.edge.rpc.processor.user.UserEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.widget.WidgetBundleEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.widget.WidgetTypeEdgeProcessor; import org.thingsboard.server.service.edge.rpc.sync.EdgeRequestsService; -import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.executors.GrpcCallbackExecutorService; +@Lazy +@Data @Component @TbCoreComponent -@Data -@Lazy public class EdgeContextComponent { + // services @Autowired - private TbClusterService clusterService; + private AdminSettingsService adminSettingsService; @Autowired - private EdgeService edgeService; + private AlarmCommentService alarmCommentService; - @Autowired(required = false) - private EdgeRpcService edgeRpcService; + @Autowired + private AlarmService alarmService; @Autowired - private EdgeEventService edgeEventService; + private AssetProfileService assetProfileService; @Autowired - private AdminSettingsService adminSettingsService; + private AssetService assetService; @Autowired - private DeviceService deviceService; + private AttributesService attributesService; @Autowired - private AssetService assetService; + private CustomerService customerService; @Autowired - private EntityViewService entityViewService; + private DashboardService dashboardService; + + @Autowired + private DeviceCredentialsService deviceCredentialsService; @Autowired private DeviceProfileService deviceProfileService; @Autowired - private AssetProfileService assetProfileService; + private DeviceService deviceService; @Autowired - private AttributesService attributesService; + private DomainService domainService; @Autowired - private DashboardService dashboardService; + private EdgeEventService edgeEventService; @Autowired - private RuleChainService ruleChainService; + private EdgeRequestsService edgeRequestsService; + + @Autowired(required = false) + private EdgeRpcService edgeRpcService; @Autowired - private UserService userService; + private EdgeService edgeService; @Autowired - private CustomerService customerService; + private EntityViewService entityViewService; @Autowired - private WidgetTypeService widgetTypeService; + private NotificationRuleService notificationRuleService; @Autowired - private WidgetsBundleService widgetsBundleService; + private NotificationTargetService notificationTargetService; @Autowired - private EdgeRequestsService edgeRequestsService; + private NotificationTemplateService notificationTemplateService; + + @Autowired + private OAuth2ClientService oAuth2ClientService; @Autowired private OtaPackageService otaPackageService; @Autowired - private TenantService tenantService; + private QueueService queueService; @Autowired - private TenantProfileService tenantProfileService; + private RateLimitService rateLimitService; @Autowired - private QueueService queueService; + private RelationService relationService; @Autowired private ResourceService resourceService; @Autowired - private NotificationRuleService notificationRuleService; + private RuleChainService ruleChainService; @Autowired - private NotificationTargetService notificationTargetService; + private TbClusterService clusterService; @Autowired - private NotificationTemplateService notificationTemplateService; + private TenantProfileService tenantProfileService; @Autowired - private DomainService domainService; + private TenantService tenantService; @Autowired - private RateLimitService rateLimitService; + private UserService userService; @Autowired - private NotificationRuleProcessor notificationRuleProcessor; + private WidgetTypeService widgetTypeService; + + @Autowired + private WidgetsBundleService widgetsBundleService; + + + // processors + @Autowired + private AdminSettingsEdgeProcessor adminSettingsProcessor; @Autowired private AlarmEdgeProcessor alarmProcessor; @Autowired - private DeviceProfileEdgeProcessor deviceProfileProcessor; + private AssetEdgeProcessor assetProcessor; @Autowired private AssetProfileEdgeProcessor assetProfileProcessor; @Autowired - private EdgeProcessor edgeProcessor; + private CustomerEdgeProcessor customerProcessor; @Autowired - private DeviceEdgeProcessor deviceProcessor; + private DashboardEdgeProcessor dashboardProcessor; @Autowired - private AssetEdgeProcessor assetProcessor; + private DeviceEdgeProcessor deviceProcessor; @Autowired - private EntityViewEdgeProcessor entityViewProcessor; + private DeviceProfileEdgeProcessor deviceProfileProcessor; @Autowired - private UserEdgeProcessor userProcessor; + private EdgeProcessor edgeProcessor; @Autowired - private RelationEdgeProcessor relationProcessor; + private EntityViewEdgeProcessor entityViewProcessor; @Autowired - private TelemetryEdgeProcessor telemetryProcessor; + private NotificationEdgeProcessor notificationEdgeProcessor; @Autowired - private DashboardEdgeProcessor dashboardProcessor; + private NotificationRuleProcessor notificationRuleProcessor; @Autowired - private RuleChainEdgeProcessor ruleChainProcessor; + private OAuth2EdgeProcessor oAuth2EdgeProcessor; @Autowired - private CustomerEdgeProcessor customerProcessor; + private OtaPackageEdgeProcessor otaPackageProcessor; @Autowired - private WidgetBundleEdgeProcessor widgetBundleProcessor; + private QueueEdgeProcessor queueProcessor; @Autowired - private WidgetTypeEdgeProcessor widgetTypeProcessor; + private RelationEdgeProcessor relationProcessor; @Autowired - private AdminSettingsEdgeProcessor adminSettingsProcessor; + private ResourceEdgeProcessor resourceProcessor; @Autowired - private OtaPackageEdgeProcessor otaPackageProcessor; + private RuleChainEdgeProcessor ruleChainProcessor; @Autowired - private QueueEdgeProcessor queueProcessor; + private TelemetryEdgeProcessor telemetryProcessor; @Autowired private TenantEdgeProcessor tenantProcessor; @@ -240,23 +265,28 @@ public class EdgeContextComponent { private TenantProfileEdgeProcessor tenantProfileProcessor; @Autowired - private ResourceEdgeProcessor resourceProcessor; + private UserEdgeProcessor userProcessor; @Autowired - private NotificationEdgeProcessor notificationEdgeProcessor; + private WidgetBundleEdgeProcessor widgetBundleProcessor; @Autowired - private OAuth2EdgeProcessor oAuth2EdgeProcessor; + private WidgetTypeEdgeProcessor widgetTypeProcessor; + // msg constructors @Autowired private EdgeMsgConstructor edgeMsgConstructor; + // factories @Autowired private AlarmEdgeProcessorFactory alarmEdgeProcessorFactory; @Autowired private AssetEdgeProcessorFactory assetEdgeProcessorFactory; + @Autowired + private AssetMsgConstructorFactory assetMsgConstructorFactory; + @Autowired private AssetProfileEdgeProcessorFactory assetProfileEdgeProcessorFactory; @@ -266,6 +296,9 @@ public class EdgeContextComponent { @Autowired private DeviceEdgeProcessorFactory deviceEdgeProcessorFactory; + @Autowired + private DeviceMsgConstructorFactory deviceMsgConstructorFactory; + @Autowired private DeviceProfileEdgeProcessorFactory deviceProfileEdgeProcessorFactory; @@ -278,12 +311,12 @@ public class EdgeContextComponent { @Autowired private ResourceEdgeProcessorFactory resourceEdgeProcessorFactory; + // config @Autowired private EdgeEventStorageSettings edgeEventStorageSettings; - @Autowired - private DbCallbackExecutorService dbCallbackExecutor; - + // callback @Autowired private GrpcCallbackExecutorService grpcCallbackExecutorService; + } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java b/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java index 893bd13171..9317c51a64 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java @@ -65,14 +65,15 @@ import org.thingsboard.server.dao.tenant.TenantService; * future.addCallback(eventPublisher.publishEvent(...)) * } * */ +@Slf4j @Component @RequiredArgsConstructor -@Slf4j public class EdgeEventSourcingListener { private final TbClusterService tbClusterService; - private final EdgeSynchronizationManager edgeSynchronizationManager; + private final TenantService tenantService; + private final EdgeSynchronizationManager edgeSynchronizationManager; @PostConstruct public void init() { @@ -106,7 +107,7 @@ public class EdgeEventSourcingListener { return; } try { - if (EntityType.EDGE.equals(entityType) || EntityType.TENANT.equals(entityType)) { + if (EntityType.TENANT.equals(entityType) || EntityType.EDGE.equals(entityType)) { return; } log.trace("[{}] DeleteEntityEvent called: {}", tenantId, event); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java index 5d45e1c3d5..9038f004d3 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java @@ -58,9 +58,12 @@ import org.thingsboard.server.gen.edge.v1.EdgeRpcServiceGrpc; import org.thingsboard.server.gen.edge.v1.RequestMsg; import org.thingsboard.server.gen.edge.v1.ResponseMsg; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; +import org.thingsboard.server.queue.discovery.TopicService; +import org.thingsboard.server.queue.kafka.TbKafkaSettings; +import org.thingsboard.server.queue.kafka.TbKafkaTopicConfigs; +import org.thingsboard.server.queue.provider.TbCoreQueueFactory; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.edge.EdgeContextComponent; -import org.thingsboard.server.service.state.DefaultDeviceStateService; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import java.io.IOException; @@ -68,6 +71,7 @@ import java.io.InputStream; import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -79,6 +83,10 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Consumer; +import static org.thingsboard.server.service.state.DefaultDeviceStateService.ACTIVITY_STATE; +import static org.thingsboard.server.service.state.DefaultDeviceStateService.LAST_CONNECT_TIME; +import static org.thingsboard.server.service.state.DefaultDeviceStateService.LAST_DISCONNECT_TIME; + @Service @Slf4j @ConditionalOnProperty(prefix = "edges", value = "enabled", havingValue = "true") @@ -90,6 +98,7 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i private final Map sessionNewEvents = new HashMap<>(); private final ConcurrentMap> sessionEdgeEventChecks = new ConcurrentHashMap<>(); private final ConcurrentMap> localSyncEdgeRequests = new ConcurrentHashMap<>(); + private final ConcurrentMap edgeEventsMigrationProcessed = new ConcurrentHashMap<>(); @Value("${edges.rpc.port}") private int rpcPort; @@ -134,6 +143,18 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i @Autowired private TbTransactionalCache edgeIdServiceIdCache; + @Autowired + private TopicService topicService; + + @Autowired + private TbCoreQueueFactory tbCoreQueueFactory; + + @Autowired + private Optional kafkaSettings; + + @Autowired + private Optional kafkaTopicConfigs; + private Server server; private ScheduledExecutorService edgeEventProcessingExecutorService; @@ -202,13 +223,16 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i @Override public StreamObserver handleMsgs(StreamObserver outputStream) { - return new EdgeGrpcSession(ctx, - outputStream, - this::onEdgeConnect, - this::onEdgeDisconnect, - sendDownlinkExecutorService, - this.maxInboundMessageSize, - this.maxHighPriorityQueueSizePerSession).getInputStream(); + EdgeGrpcSession session = createEdgeGrpcSession(outputStream); + return session.getInputStream(); + } + + private EdgeGrpcSession createEdgeGrpcSession(StreamObserver outputStream) { + return kafkaSettings.isPresent() && kafkaTopicConfigs.isPresent() + ? new KafkaEdgeGrpcSession(ctx, topicService, tbCoreQueueFactory, kafkaSettings.get(), kafkaTopicConfigs.get(), outputStream, this::onEdgeConnect, this::onEdgeDisconnect, + sendDownlinkExecutorService, maxInboundMessageSize, maxHighPriorityQueueSizePerSession) + : new PostgresEdgeGrpcSession(ctx, outputStream, this::onEdgeConnect, this::onEdgeDisconnect, + sendDownlinkExecutorService, maxInboundMessageSize, maxHighPriorityQueueSizePerSession); } @Override @@ -253,6 +277,8 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i EdgeGrpcSession session = sessions.get(edgeId); if (session != null && session.isConnected()) { log.info("[{}] Closing and removing session for edge [{}]", tenantId, edgeId); + session.destroy(); + session.cleanUp(); session.close(); sessions.remove(edgeId); final Lock newEventLock = sessionNewEventsLocks.computeIfAbsent(edgeId, id -> new ReentrantLock()); @@ -311,12 +337,13 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i } finally { newEventLock.unlock(); } - save(tenantId, edgeId, DefaultDeviceStateService.ACTIVITY_STATE, true); + save(tenantId, edgeId, ACTIVITY_STATE, true); long lastConnectTs = System.currentTimeMillis(); - save(tenantId, edgeId, DefaultDeviceStateService.LAST_CONNECT_TIME, lastConnectTs); + save(tenantId, edgeId, LAST_CONNECT_TIME, lastConnectTs); edgeIdServiceIdCache.put(edgeId, serviceInfoProvider.getServiceId()); pushRuleEngineMessage(tenantId, edge, lastConnectTs, TbMsgType.CONNECT_EVENT); cancelScheduleEdgeEventsCheck(edgeId); + edgeEventsMigrationProcessed.putIfAbsent(edgeId, Boolean.FALSE); scheduleEdgeEventsCheck(edgeGrpcSession); } @@ -377,7 +404,7 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i private void scheduleEdgeEventsCheck(EdgeGrpcSession session) { EdgeId edgeId = session.getEdge().getId(); - UUID tenantId = session.getEdge().getTenantId().getId(); + TenantId tenantId = session.getEdge().getTenantId(); if (sessions.containsKey(edgeId)) { ScheduledFuture edgeEventCheckTask = edgeEventProcessingExecutorService.schedule(() -> { try { @@ -387,6 +414,8 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i if (Boolean.TRUE.equals(sessionNewEvents.get(edgeId))) { log.trace("[{}][{}] Set session new events flag to false", tenantId, edgeId.getId()); sessionNewEvents.put(edgeId, false); + processEdgeEventMigrationIfNeeded(session, edgeId); + session.processHighPriorityEvents(); Futures.addCallback(session.processEdgeEvents(), new FutureCallback<>() { @Override public void onSuccess(Boolean newEventsAdded) { @@ -420,6 +449,21 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i } } + private void processEdgeEventMigrationIfNeeded(EdgeGrpcSession session, EdgeId edgeId) throws Exception { + boolean isMigrationProcessed = edgeEventsMigrationProcessed.getOrDefault(edgeId, Boolean.FALSE); + if (!isMigrationProcessed) { + Boolean eventsExist = session.migrateEdgeEvents().get(); + if (Boolean.TRUE.equals(eventsExist)) { + sessionNewEvents.put(edgeId, true); + scheduleEdgeEventsCheck(session); + } else if (Boolean.FALSE.equals(eventsExist)) { + edgeEventsMigrationProcessed.put(edgeId, true); + } else { + scheduleEdgeEventsCheck(session); + } + } + } + private void cancelScheduleEdgeEventsCheck(EdgeId edgeId) { log.trace("[{}] cancelling edge event check for edge", edgeId); if (sessionEdgeEventChecks.containsKey(edgeId)) { @@ -444,10 +488,11 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i } finally { newEventLock.unlock(); } + toRemove.destroy(); TenantId tenantId = toRemove.getEdge().getTenantId(); - save(tenantId, edgeId, DefaultDeviceStateService.ACTIVITY_STATE, false); + save(tenantId, edgeId, ACTIVITY_STATE, false); long lastDisconnectTs = System.currentTimeMillis(); - save(tenantId, edgeId, DefaultDeviceStateService.LAST_DISCONNECT_TIME, lastDisconnectTs); + save(tenantId, edgeId, LAST_DISCONNECT_TIME, lastDisconnectTs); pushRuleEngineMessage(toRemove.getEdge().getTenantId(), edge, lastDisconnectTs, TbMsgType.DISCONNECT_EVENT); cancelScheduleEdgeEventsCheck(edgeId); } else { @@ -481,6 +526,7 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i } private static class AttributeSaveCallback implements FutureCallback { + private final TenantId tenantId; private final EdgeId edgeId; private final String key; @@ -502,6 +548,7 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i public void onFailure(Throwable t) { log.warn("[{}][{}] Failed to update attribute [{}] with value [{}]", tenantId, edgeId, key, value, t); } + } private void pushRuleEngineMessage(TenantId tenantId, Edge edge, long ts, TbMsgType msgType) { @@ -510,11 +557,11 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i ObjectNode edgeState = JacksonUtil.newObjectNode(); boolean isConnected = TbMsgType.CONNECT_EVENT.equals(msgType); if (isConnected) { - edgeState.put(DefaultDeviceStateService.ACTIVITY_STATE, true); - edgeState.put(DefaultDeviceStateService.LAST_CONNECT_TIME, ts); + edgeState.put(ACTIVITY_STATE, true); + edgeState.put(LAST_CONNECT_TIME, ts); } else { - edgeState.put(DefaultDeviceStateService.ACTIVITY_STATE, false); - edgeState.put(DefaultDeviceStateService.LAST_DISCONNECT_TIME, ts); + edgeState.put(ACTIVITY_STATE, false); + edgeState.put(LAST_DISCONNECT_TIME, ts); } ctx.getNotificationRuleProcessor().process(EdgeConnectionTrigger.builder() .tenantId(tenantId) 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 d0d10f0174..f0bcbf2768 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 @@ -105,30 +105,26 @@ import java.util.function.BiConsumer; @Slf4j @Data -public final class EdgeGrpcSession implements Closeable { - - private static final ReentrantLock downlinkMsgLock = new ReentrantLock(); - private static final ConcurrentLinkedQueue highPriorityQueue = new ConcurrentLinkedQueue<>(); - - private static final int MAX_DOWNLINK_ATTEMPTS = 10; // max number of attempts to send downlink message if edge connected +public abstract class EdgeGrpcSession implements Closeable { private static final String QUEUE_START_TS_ATTR_KEY = "queueStartTs"; private static final String QUEUE_START_SEQ_ID_ATTR_KEY = "queueStartSeqId"; + + private static final int MAX_DOWNLINK_ATTEMPTS = 10; private static final String RATE_LIMIT_REACHED = "Rate limit reached"; - private final UUID sessionId; - private final BiConsumer sessionOpenListener; - private final BiConsumer sessionCloseListener; + protected static final ConcurrentLinkedQueue highPriorityQueue = new ConcurrentLinkedQueue<>(); + + protected UUID sessionId; + private BiConsumer sessionOpenListener; + private BiConsumer sessionCloseListener; private final EdgeSessionState sessionState = new EdgeSessionState(); + private final ReentrantLock downlinkMsgLock = new ReentrantLock(); - private EdgeContextComponent ctx; - private Edge edge; - private TenantId tenantId; - private StreamObserver inputStream; - private StreamObserver outputStream; - private boolean connected; - private volatile boolean syncCompleted; + protected EdgeContextComponent ctx; + protected Edge edge; + protected TenantId tenantId; private Long newStartTs; private Long previousStartTs; @@ -136,16 +132,24 @@ public final class EdgeGrpcSession implements Closeable { private Long previousStartSeqId; private Long seqIdEnd; - private EdgeVersion edgeVersion; + private StreamObserver inputStream; + private StreamObserver outputStream; + private boolean connected; + private volatile boolean syncCompleted; + + private EdgeVersion edgeVersion; private int maxInboundMessageSize; private int clientMaxInboundMessageSize; private int maxHighPriorityQueueSizePerSession; private ScheduledExecutorService sendDownlinkExecutorService; - EdgeGrpcSession(EdgeContextComponent ctx, StreamObserver outputStream, BiConsumer sessionOpenListener, - BiConsumer sessionCloseListener, ScheduledExecutorService sendDownlinkExecutorService, int maxInboundMessageSize, int maxHighPriorityQueueSizePerSession) { + public EdgeGrpcSession(EdgeContextComponent ctx, StreamObserver outputStream, + BiConsumer sessionOpenListener, + BiConsumer sessionCloseListener, + ScheduledExecutorService sendDownlinkExecutorService, + int maxInboundMessageSize, int maxHighPriorityQueueSizePerSession) { this.sessionId = UUID.randomUUID(); this.ctx = ctx; this.outputStream = outputStream; @@ -157,8 +161,10 @@ public final class EdgeGrpcSession implements Closeable { initInputStream(); } - private void initInputStream() { - this.inputStream = new StreamObserver<>() { + protected abstract ListenableFuture migrateEdgeEvents() throws Exception; + + public void initInputStream() { + inputStream = new StreamObserver<>() { @Override public void onNext(RequestMsg requestMsg) { if (!connected && requestMsg.getMsgType().equals(RequestMsgType.CONNECT_RPC_MESSAGE)) { @@ -227,8 +233,20 @@ public final class EdgeGrpcSession implements Closeable { }; } + public void onConfigurationUpdate(Edge edge) { + log.debug("[{}] onConfigurationUpdate [{}]", sessionId, edge); + this.tenantId = edge.getTenantId(); + this.edge = edge; + EdgeUpdateMsg edgeConfig = EdgeUpdateMsg.newBuilder() + .setConfiguration(ctx.getEdgeMsgConstructor().constructEdgeConfiguration(edge)).build(); + ResponseMsg edgeConfigMsg = ResponseMsg.newBuilder() + .setEdgeUpdateMsg(edgeConfig) + .build(); + sendDownlinkMsg(edgeConfigMsg); + } + public void startSyncProcess(boolean fullSync) { - log.info("[{}][{}][{}] Staring edge sync process", this.tenantId, edge.getId(), this.sessionId); + log.info("[{}][{}][{}] Staring edge sync process", tenantId, edge.getId(), sessionId); syncCompleted = false; interruptGeneralProcessingOnSync(); doSync(new EdgeSyncCursor(ctx, edge, fullSync)); @@ -238,7 +256,7 @@ public final class EdgeGrpcSession implements Closeable { if (cursor.hasNext()) { EdgeEventFetcher next = cursor.getNext(); log.info("[{}][{}] starting sync process, cursor current idx = {}, class = {}", - this.tenantId, edge.getId(), cursor.getCurrentIdx(), next.getClass().getSimpleName()); + tenantId, edge.getId(), cursor.getCurrentIdx(), next.getClass().getSimpleName()); ListenableFuture> future = startProcessingEdgeEvents(next); Futures.addCallback(future, new FutureCallback<>() { @Override @@ -252,7 +270,7 @@ public final class EdgeGrpcSession implements Closeable { } }, ctx.getGrpcCallbackExecutorService()); } else { - log.info("[{}][{}] sync process completed", this.tenantId, edge.getId()); + log.info("[{}][{}] sync process completed", tenantId, edge.getId()); DownlinkMsg syncCompleteDownlinkMsg = DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .setSyncCompletedMsg(SyncCompletedMsg.newBuilder().build()) @@ -272,179 +290,14 @@ public final class EdgeGrpcSession implements Closeable { } } - private void markSyncCompletedSendEdgeEventUpdate() { - syncCompleted = true; - ctx.getClusterService().onEdgeEventUpdate(new EdgeEventUpdateMsg(edge.getTenantId(), edge.getId())); - } - - private void onUplinkMsg(UplinkMsg uplinkMsg) { - if (isRateLimitViolated(uplinkMsg)) { - return; - } - ListenableFuture> future = processUplinkMsg(uplinkMsg); - Futures.addCallback(future, new FutureCallback<>() { - @Override - public void onSuccess(@Nullable List result) { - sendResponseMessage(uplinkMsg.getUplinkMsgId(), true, null); - } - - @Override - public void onFailure(Throwable t) { - String errorMsg = EdgeUtils.createErrorMsgFromRootCauseAndStackTrace(t); - sendResponseMessage(uplinkMsg.getUplinkMsgId(), false, errorMsg); - } - }, ctx.getGrpcCallbackExecutorService()); - } - - private boolean isRateLimitViolated(UplinkMsg uplinkMsg) { - if (!ctx.getRateLimitService().checkRateLimit(LimitedApi.EDGE_UPLINK_MESSAGES, tenantId) || - !ctx.getRateLimitService().checkRateLimit(LimitedApi.EDGE_UPLINK_MESSAGES_PER_EDGE, tenantId, edge.getId())) { - String errorMsg = String.format("Failed to process uplink message. %s", RATE_LIMIT_REACHED); - sendResponseMessage(uplinkMsg.getUplinkMsgId(), false, errorMsg); - return true; - } - return false; - } - - private void sendResponseMessage(int uplinkMsgId, boolean success, String errorMsg) { - UplinkResponseMsg.Builder responseBuilder = UplinkResponseMsg.newBuilder() - .setUplinkMsgId(uplinkMsgId) - .setSuccess(success); - if (errorMsg != null) { - responseBuilder.setErrorMsg(errorMsg); - } - sendDownlinkMsg(ResponseMsg.newBuilder() - .setUplinkResponseMsg(responseBuilder.build()) - .build()); - } - - private void onDownlinkResponse(DownlinkResponseMsg msg) { - try { - if (msg.getSuccess()) { - sessionState.getPendingMsgsMap().remove(msg.getDownlinkMsgId()); - log.debug("[{}][{}] Msg has been processed successfully! Msg Id: [{}], Msg: {}", this.tenantId, edge.getRoutingKey(), msg.getDownlinkMsgId(), msg); - } else { - log.error("[{}][{}] Msg processing failed! Msg Id: [{}], Error msg: {}", this.tenantId, edge.getRoutingKey(), msg.getDownlinkMsgId(), msg.getErrorMsg()); - } - if (sessionState.getPendingMsgsMap().isEmpty()) { - log.debug("[{}][{}] Pending msgs map is empty. Stopping current iteration", this.tenantId, edge.getRoutingKey()); - stopCurrentSendDownlinkMsgsTask(false); - } - } catch (Exception e) { - log.error("[{}][{}] Can't process downlink response message [{}]", this.tenantId, this.sessionId, msg, e); - } - } - - private void sendDownlinkMsg(ResponseMsg downlinkMsg) { - if (downlinkMsg.getDownlinkMsg().getWidgetTypeUpdateMsgCount() > 0) { - log.trace("[{}][{}] Sending downlink widgetTypeUpdateMsg, downlinkMsgId = {}", this.tenantId, this.sessionId, downlinkMsg.getDownlinkMsg().getDownlinkMsgId()); - } else { - log.trace("[{}][{}] Sending downlink msg [{}]", this.tenantId, this.sessionId, downlinkMsg); - } - if (isConnected()) { - downlinkMsgLock.lock(); - try { - outputStream.onNext(downlinkMsg); - } catch (Exception e) { - log.error("[{}][{}] Failed to send downlink message [{}]", this.tenantId, this.sessionId, downlinkMsg, e); - connected = false; - sessionCloseListener.accept(edge, sessionId); - } finally { - downlinkMsgLock.unlock(); - } - log.trace("[{}][{}] Response msg successfully sent. downlinkMsgId = {}", this.tenantId, this.sessionId, downlinkMsg.getDownlinkMsg().getDownlinkMsgId()); - } - } - - void onConfigurationUpdate(Edge edge) { - log.debug("[{}] onConfigurationUpdate [{}]", this.sessionId, edge); - this.edge = edge; - this.tenantId = edge.getTenantId(); - EdgeUpdateMsg edgeConfig = EdgeUpdateMsg.newBuilder() - .setConfiguration(ctx.getEdgeMsgConstructor().constructEdgeConfiguration(edge)).build(); - ResponseMsg edgeConfigMsg = ResponseMsg.newBuilder() - .setEdgeUpdateMsg(edgeConfig) - .build(); - sendDownlinkMsg(edgeConfigMsg); - } - - ListenableFuture processEdgeEvents() throws Exception { - SettableFuture result = SettableFuture.create(); - log.trace("[{}][{}] starting processing edge events", this.tenantId, this.sessionId); - if (isConnected() && isSyncCompleted()) { - Pair startTsAndSeqId = getQueueStartTsAndSeqId().get(); - this.previousStartTs = startTsAndSeqId.getFirst(); - this.previousStartSeqId = startTsAndSeqId.getSecond(); - GeneralEdgeEventFetcher fetcher = new GeneralEdgeEventFetcher( - this.previousStartTs, - this.previousStartSeqId, - this.seqIdEnd, - false, - Integer.toUnsignedLong(ctx.getEdgeEventStorageSettings().getMaxReadRecordsCount()), - ctx.getEdgeEventService()); - Futures.addCallback(startProcessingEdgeEvents(fetcher), new FutureCallback<>() { - @Override - public void onSuccess(@Nullable Pair newStartTsAndSeqId) { - if (newStartTsAndSeqId != null) { - ListenableFuture> updateFuture = updateQueueStartTsAndSeqId(newStartTsAndSeqId); - Futures.addCallback(updateFuture, new FutureCallback<>() { - @Override - public void onSuccess(@Nullable List list) { - log.debug("[{}][{}] queue offset was updated [{}]", tenantId, sessionId, newStartTsAndSeqId); - if (fetcher.isSeqIdNewCycleStarted()) { - seqIdEnd = fetcher.getSeqIdEnd(); - boolean newEventsAvailable = isNewEdgeEventsAvailable(); - result.set(newEventsAvailable); - } else { - seqIdEnd = null; - boolean newEventsAvailable = isSeqIdStartedNewCycle(); - if (!newEventsAvailable) { - newEventsAvailable = isNewEdgeEventsAvailable(); - } - result.set(newEventsAvailable); - } - } - - @Override - public void onFailure(Throwable t) { - log.error("[{}][{}] Failed to update queue offset [{}]", tenantId, sessionId, newStartTsAndSeqId, t); - result.setException(t); - } - }, ctx.getGrpcCallbackExecutorService()); - } else { - log.trace("[{}][{}] newStartTsAndSeqId is null. Skipping iteration without db update", tenantId, sessionId); - result.set(null); - } - } - - @Override - public void onFailure(Throwable t) { - log.error("[{}][{}] Failed to process events", tenantId, sessionId, t); - result.setException(t); - } - }, ctx.getGrpcCallbackExecutorService()); - } else { - log.trace("[{}][{}] edge is not connected or sync is not completed. Skipping iteration", tenantId, sessionId); - result.set(null); - } - return result; - } - - private ListenableFuture> startProcessingEdgeEvents(EdgeEventFetcher fetcher) { - SettableFuture> result = SettableFuture.create(); - PageLink pageLink = fetcher.getPageLink(ctx.getEdgeEventStorageSettings().getMaxReadRecordsCount()); - processEdgeEvents(fetcher, pageLink, result); - return result; - } - - private void processEdgeEvents(EdgeEventFetcher fetcher, PageLink pageLink, SettableFuture> result) { + protected void processEdgeEvents(EdgeEventFetcher fetcher, PageLink pageLink, SettableFuture> result) { try { if (!highPriorityQueue.isEmpty()) { processHighPriorityEvents(); } PageData pageData = fetcher.fetchEdgeEvents(edge.getTenantId(), edge, pageLink); if (isConnected() && !pageData.getData().isEmpty()) { - log.trace("[{}][{}][{}] event(s) are going to be processed.", this.tenantId, this.sessionId, pageData.getData().size()); + log.trace("[{}][{}][{}] event(s) are going to be processed.", tenantId, sessionId, pageData.getData().size()); List downlinkMsgsPack = convertToDownlinkMsgsPack(pageData.getData()); Futures.addCallback(sendDownlinkMsgsPack(downlinkMsgsPack), new FutureCallback<>() { @Override @@ -476,30 +329,69 @@ public final class EdgeGrpcSession implements Closeable { } }, ctx.getGrpcCallbackExecutorService()); } else { - log.trace("[{}] no event(s) found. Stop processing edge events", this.sessionId); + log.trace("[{}] no event(s) found. Stop processing edge events", sessionId); result.set(null); } } catch (Exception e) { - log.error("[{}] Failed to fetch edge events", this.sessionId, e); + log.error("[{}] Failed to fetch edge events", sessionId, e); result.setException(e); } } - private void processHighPriorityEvents() { - try { - List highPriorityEvents = new ArrayList<>(); - EdgeEvent event; - while ((event = highPriorityQueue.poll()) != null) { - highPriorityEvents.add(event); + private ConnectResponseMsg processConnect(ConnectRequestMsg request) { + log.trace("[{}] processConnect [{}]", sessionId, request); + Optional optional = ctx.getEdgeService().findEdgeByRoutingKey(TenantId.SYS_TENANT_ID, request.getEdgeRoutingKey()); + if (optional.isPresent()) { + edge = optional.get(); + tenantId = edge.getTenantId(); + try { + if (edge.getSecret().equals(request.getEdgeSecret())) { + sessionOpenListener.accept(edge.getId(), this); + edgeVersion = request.getEdgeVersion(); + processSaveEdgeVersionAsAttribute(request.getEdgeVersion().name()); + return ConnectResponseMsg.newBuilder() + .setResponseCode(ConnectResponseCode.ACCEPTED) + .setErrorMsg("") + .setConfiguration(ctx.getEdgeMsgConstructor().constructEdgeConfiguration(edge)) + .setMaxInboundMessageSize(maxInboundMessageSize) + .build(); + } + String error = "Failed to validate the edge!"; + String failureMsg = String.format("%s Provided request secret: %s", error, request.getEdgeSecret()); + ctx.getNotificationRuleProcessor().process(EdgeCommunicationFailureTrigger.builder().tenantId(tenantId).edgeId(edge.getId()) + .customerId(edge.getCustomerId()).edgeName(edge.getName()).failureMsg(failureMsg).error(error).build()); + return ConnectResponseMsg.newBuilder() + .setResponseCode(ConnectResponseCode.BAD_CREDENTIALS) + .setErrorMsg(failureMsg) + .setConfiguration(EdgeConfiguration.getDefaultInstance()).build(); + } catch (Exception e) { + String failureMsg = "Failed to process edge connection!"; + ctx.getNotificationRuleProcessor().process(EdgeCommunicationFailureTrigger.builder().tenantId(tenantId).edgeId(edge.getId()) + .customerId(edge.getCustomerId()).edgeName(edge.getName()).failureMsg(failureMsg).error(e.getMessage()).build()); + log.error(failureMsg, e); + return ConnectResponseMsg.newBuilder() + .setResponseCode(ConnectResponseCode.SERVER_UNAVAILABLE) + .setErrorMsg(failureMsg) + .setConfiguration(EdgeConfiguration.getDefaultInstance()).build(); } - List downlinkMsgsPack = convertToDownlinkMsgsPack(highPriorityEvents); - sendDownlinkMsgsPack(downlinkMsgsPack).get(); - } catch (Exception e) { - log.error("[{}] Failed to process high priority events", this.sessionId, e); } + return ConnectResponseMsg.newBuilder() + .setResponseCode(ConnectResponseCode.BAD_CREDENTIALS) + .setErrorMsg("Failed to find the edge! Routing key: " + request.getEdgeRoutingKey()) + .setConfiguration(EdgeConfiguration.getDefaultInstance()).build(); } - private ListenableFuture sendDownlinkMsgsPack(List downlinkMsgsPack) { + private void processSaveEdgeVersionAsAttribute(String edgeVersion) { + AttributeKvEntry attributeKvEntry = new BaseAttributeKvEntry(new StringDataEntry(DataConstants.EDGE_VERSION_ATTR_KEY, edgeVersion), System.currentTimeMillis()); + ctx.getAttributesService().save(tenantId, edge.getId(), AttributeScope.SERVER_SCOPE, attributeKvEntry); + } + + private void interruptGeneralProcessingOnSync() { + log.debug("[{}][{}][{}] Sync process started. General processing interrupted!", tenantId, edge.getId(), sessionId); + stopCurrentSendDownlinkMsgsTask(true); + } + + protected ListenableFuture sendDownlinkMsgsPack(List downlinkMsgsPack) { interruptPreviousSendDownlinkMsgsTask(); sessionState.setSendDownlinkMsgsFuture(SettableFuture.create()); @@ -511,10 +403,47 @@ public final class EdgeGrpcSession implements Closeable { return sessionState.getSendDownlinkMsgsFuture(); } + private void interruptPreviousSendDownlinkMsgsTask() { + if (sessionState.getSendDownlinkMsgsFuture() != null && !sessionState.getSendDownlinkMsgsFuture().isDone() + || sessionState.getScheduledSendDownlinkTask() != null && !sessionState.getScheduledSendDownlinkTask().isCancelled()) { + log.debug("[{}][{}][{}] Previous send downlink future was not properly completed, stopping it now!", tenantId, edge.getId(), sessionId); + stopCurrentSendDownlinkMsgsTask(true); + } + } + + private void onUplinkMsg(UplinkMsg uplinkMsg) { + if (isRateLimitViolated(uplinkMsg)) { + return; + } + ListenableFuture> future = processUplinkMsg(uplinkMsg); + Futures.addCallback(future, new FutureCallback<>() { + @Override + public void onSuccess(@Nullable List result) { + sendResponseMessage(uplinkMsg.getUplinkMsgId(), true, null); + } + + @Override + public void onFailure(Throwable t) { + String errorMsg = EdgeUtils.createErrorMsgFromRootCauseAndStackTrace(t); + sendResponseMessage(uplinkMsg.getUplinkMsgId(), false, errorMsg); + } + }, ctx.getGrpcCallbackExecutorService()); + } + + private boolean isRateLimitViolated(UplinkMsg uplinkMsg) { + if (!ctx.getRateLimitService().checkRateLimit(LimitedApi.EDGE_UPLINK_MESSAGES, tenantId) || + !ctx.getRateLimitService().checkRateLimit(LimitedApi.EDGE_UPLINK_MESSAGES_PER_EDGE, tenantId, edge.getId())) { + String errorMsg = String.format("Failed to process uplink message. %s", RATE_LIMIT_REACHED); + sendResponseMessage(uplinkMsg.getUplinkMsgId(), false, errorMsg); + return true; + } + return false; + } + private void scheduleDownlinkMsgsPackSend(int attempt) { Runnable sendDownlinkMsgsTask = () -> { try { - if (isConnected() && sessionState.getPendingMsgsMap().values().size() > 0) { + if (isConnected() && !sessionState.getPendingMsgsMap().values().isEmpty()) { List copy = new ArrayList<>(sessionState.getPendingMsgsMap().values()); if (attempt > 1) { String error = "Failed to deliver the batch"; @@ -525,16 +454,16 @@ public final class EdgeGrpcSession implements Closeable { ctx.getNotificationRuleProcessor().process(EdgeCommunicationFailureTrigger.builder().tenantId(tenantId) .edgeId(edge.getId()).customerId(edge.getCustomerId()).edgeName(edge.getName()).failureMsg(failureMsg).error(error).build()); } - log.warn("[{}][{}] {}, attempt: {}", this.tenantId, this.sessionId, failureMsg, attempt); + log.warn("[{}][{}] {}, attempt: {}", tenantId, sessionId, failureMsg, attempt); } - log.trace("[{}][{}][{}] downlink msg(s) are going to be send.", this.tenantId, this.sessionId, copy.size()); + log.trace("[{}][{}][{}] downlink msg(s) are going to be send.", tenantId, sessionId, copy.size()); for (DownlinkMsg downlinkMsg : copy) { - if (this.clientMaxInboundMessageSize != 0 && downlinkMsg.getSerializedSize() > this.clientMaxInboundMessageSize) { + if (clientMaxInboundMessageSize != 0 && downlinkMsg.getSerializedSize() > clientMaxInboundMessageSize) { String error = String.format("Client max inbound message size %s is exceeded. Please increase value of CLOUD_RPC_MAX_INBOUND_MESSAGE_SIZE " + - "env variable on the edge and restart it.", this.clientMaxInboundMessageSize); + "env variable on the edge and restart it.", clientMaxInboundMessageSize); String message = String.format("Downlink msg size %s exceeds client max inbound message size %s. " + - "Please increase value of CLOUD_RPC_MAX_INBOUND_MESSAGE_SIZE env variable on the edge and restart it.", downlinkMsg.getSerializedSize(), this.clientMaxInboundMessageSize); - log.error("[{}][{}][{}] {} Message {}", this.tenantId, edge.getId(), this.sessionId, message, downlinkMsg); + "Please increase value of CLOUD_RPC_MAX_INBOUND_MESSAGE_SIZE env variable on the edge and restart it.", downlinkMsg.getSerializedSize(), clientMaxInboundMessageSize); + log.error("[{}][{}][{}] {} Message {}", tenantId, edge.getId(), sessionId, message, downlinkMsg); ctx.getNotificationRuleProcessor().process(EdgeCommunicationFailureTrigger.builder().tenantId(tenantId) .edgeId(edge.getId()).customerId(edge.getCustomerId()).edgeName(edge.getName()).failureMsg(message).error(error).build()); sessionState.getPendingMsgsMap().remove(downlinkMsg.getDownlinkMsgId()); @@ -549,7 +478,7 @@ public final class EdgeGrpcSession implements Closeable { } else { String failureMsg = String.format("Failed to deliver messages: %s", copy); log.warn("[{}][{}] Failed to deliver the batch after {} attempts. Next messages are going to be discarded {}", - this.tenantId, this.sessionId, MAX_DOWNLINK_ATTEMPTS, copy); + tenantId, sessionId, MAX_DOWNLINK_ATTEMPTS, copy); ctx.getNotificationRuleProcessor().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()); @@ -559,7 +488,7 @@ public final class EdgeGrpcSession implements Closeable { stopCurrentSendDownlinkMsgsTask(false); } } catch (Exception e) { - log.warn("[{}][{}] Failed to send downlink msgs. Error msg {}", this.tenantId, this.sessionId, e.getMessage(), e); + log.warn("[{}][{}] Failed to send downlink msgs. Error msg {}", tenantId, sessionId, e.getMessage(), e); stopCurrentSendDownlinkMsgsTask(true); } }; @@ -576,29 +505,134 @@ public final class EdgeGrpcSession implements Closeable { } } - private List convertToDownlinkMsgsPack(List edgeEvents) { + private void sendResponseMessage(int uplinkMsgId, boolean success, String errorMsg) { + UplinkResponseMsg.Builder responseBuilder = UplinkResponseMsg.newBuilder() + .setUplinkMsgId(uplinkMsgId) + .setSuccess(success); + if (errorMsg != null) { + responseBuilder.setErrorMsg(errorMsg); + } + sendDownlinkMsg(ResponseMsg.newBuilder() + .setUplinkResponseMsg(responseBuilder.build()) + .build()); + } + + private void onDownlinkResponse(DownlinkResponseMsg msg) { + try { + if (msg.getSuccess()) { + sessionState.getPendingMsgsMap().remove(msg.getDownlinkMsgId()); + log.debug("[{}][{}] Msg has been processed successfully! Msg Id: [{}], Msg: {}", tenantId, edge.getRoutingKey(), msg.getDownlinkMsgId(), msg); + } else { + log.error("[{}][{}] Msg processing failed! Msg Id: [{}], Error msg: {}", tenantId, edge.getRoutingKey(), msg.getDownlinkMsgId(), msg.getErrorMsg()); + } + if (sessionState.getPendingMsgsMap().isEmpty()) { + log.debug("[{}][{}] Pending msgs map is empty. Stopping current iteration", tenantId, edge.getRoutingKey()); + stopCurrentSendDownlinkMsgsTask(false); + } + } catch (Exception e) { + log.error("[{}][{}] Can't process downlink response message [{}]", tenantId, sessionId, msg, e); + } + } + + public void processHighPriorityEvents() { + try { + List highPriorityEvents = new ArrayList<>(); + EdgeEvent event; + while ((event = highPriorityQueue.poll()) != null) { + highPriorityEvents.add(event); + } + List downlinkMsgsPack = convertToDownlinkMsgsPack(highPriorityEvents); + sendDownlinkMsgsPack(downlinkMsgsPack).get(); + } catch (Exception e) { + log.error("[{}] Failed to process high priority events", sessionId, e); + } + } + + public ListenableFuture processEdgeEvents() throws Exception { + SettableFuture result = SettableFuture.create(); + log.trace("[{}][{}] starting processing edge events", tenantId, sessionId); + if (isConnected() && isSyncCompleted()) { + Pair startTsAndSeqId = getQueueStartTsAndSeqId().get(); + previousStartTs = startTsAndSeqId.getFirst(); + previousStartSeqId = startTsAndSeqId.getSecond(); + GeneralEdgeEventFetcher fetcher = new GeneralEdgeEventFetcher( + previousStartTs, + previousStartSeqId, + seqIdEnd, + false, + Integer.toUnsignedLong(ctx.getEdgeEventStorageSettings().getMaxReadRecordsCount()), + ctx.getEdgeEventService()); + Futures.addCallback(startProcessingEdgeEvents(fetcher), new FutureCallback<>() { + @Override + public void onSuccess(@Nullable Pair newStartTsAndSeqId) { + if (newStartTsAndSeqId != null) { + ListenableFuture> updateFuture = updateQueueStartTsAndSeqId(newStartTsAndSeqId); + Futures.addCallback(updateFuture, new FutureCallback<>() { + @Override + public void onSuccess(@Nullable List list) { + log.debug("[{}][{}] queue offset was updated [{}]", tenantId, sessionId, newStartTsAndSeqId); + if (fetcher.isSeqIdNewCycleStarted()) { + seqIdEnd = fetcher.getSeqIdEnd(); + boolean newEventsAvailable = isNewEdgeEventsAvailable(); + result.set(newEventsAvailable); + } else { + seqIdEnd = null; + boolean newEventsAvailable = isSeqIdStartedNewCycle(); + if (!newEventsAvailable) { + newEventsAvailable = isNewEdgeEventsAvailable(); + } + result.set(newEventsAvailable); + } + } + + @Override + public void onFailure(Throwable t) { + log.error("[{}][{}] Failed to update queue offset [{}]", tenantId, sessionId, newStartTsAndSeqId, t); + result.setException(t); + } + }, ctx.getGrpcCallbackExecutorService()); + } else { + log.trace("[{}][{}] newStartTsAndSeqId is null. Skipping iteration without db update", tenantId, sessionId); + result.set(Boolean.FALSE); + } + } + + @Override + public void onFailure(Throwable t) { + log.error("[{}][{}] Failed to process events", tenantId, sessionId, t); + result.setException(t); + } + }, ctx.getGrpcCallbackExecutorService()); + } else { + log.trace("[{}][{}] edge is not connected or sync is not completed. Skipping iteration", tenantId, sessionId); + result.set(null); + } + return result; + } + + protected List convertToDownlinkMsgsPack(List edgeEvents) { List result = new ArrayList<>(); for (EdgeEvent edgeEvent : edgeEvents) { - log.trace("[{}][{}] converting edge event to downlink msg [{}]", this.tenantId, this.sessionId, edgeEvent); + log.trace("[{}][{}] converting edge event to downlink msg [{}]", tenantId, sessionId, edgeEvent); DownlinkMsg downlinkMsg = null; try { switch (edgeEvent.getAction()) { - case UPDATED, ADDED, DELETED, ASSIGNED_TO_EDGE, UNASSIGNED_FROM_EDGE, ALARM_ACK, ALARM_CLEAR, ALARM_DELETE, - CREDENTIALS_UPDATED, RELATION_ADD_OR_UPDATE, RELATION_DELETED, RPC_CALL, - ASSIGNED_TO_CUSTOMER, UNASSIGNED_FROM_CUSTOMER, ADDED_COMMENT, UPDATED_COMMENT, DELETED_COMMENT -> { + case UPDATED, ADDED, DELETED, ASSIGNED_TO_EDGE, UNASSIGNED_FROM_EDGE, ALARM_ACK, ALARM_CLEAR, + ALARM_DELETE, CREDENTIALS_UPDATED, RELATION_ADD_OR_UPDATE, RELATION_DELETED, RPC_CALL, + ASSIGNED_TO_CUSTOMER, UNASSIGNED_FROM_CUSTOMER, ADDED_COMMENT, UPDATED_COMMENT, DELETED_COMMENT -> { downlinkMsg = convertEntityEventToDownlink(edgeEvent); if (downlinkMsg != null && downlinkMsg.getWidgetTypeUpdateMsgCount() > 0) { - log.trace("[{}][{}] widgetTypeUpdateMsg message processed, downlinkMsgId = {}", this.tenantId, this.sessionId, downlinkMsg.getDownlinkMsgId()); + log.trace("[{}][{}] widgetTypeUpdateMsg message processed, downlinkMsgId = {}", tenantId, sessionId, downlinkMsg.getDownlinkMsgId()); } else { - log.trace("[{}][{}] entity message processed [{}]", this.tenantId, this.sessionId, downlinkMsg); + log.trace("[{}][{}] entity message processed [{}]", tenantId, sessionId, downlinkMsg); } } case ATTRIBUTES_UPDATED, POST_ATTRIBUTES, ATTRIBUTES_DELETED, TIMESERIES_UPDATED -> downlinkMsg = ctx.getTelemetryProcessor().convertTelemetryEventToDownlink(edge, edgeEvent); - default -> log.warn("[{}][{}] Unsupported action type [{}]", this.tenantId, this.sessionId, edgeEvent.getAction()); + default -> log.warn("[{}][{}] Unsupported action type [{}]", tenantId, sessionId, edgeEvent.getAction()); } } catch (Exception e) { - log.error("[{}][{}] Exception during converting edge event to downlink msg", this.tenantId, this.sessionId, e); + log.error("[{}][{}] Exception during converting edge event to downlink msg", tenantId, sessionId, e); } if (downlinkMsg != null) { result.add(downlinkMsg); @@ -630,22 +664,22 @@ public final class EdgeGrpcSession implements Closeable { private boolean isSeqIdStartedNewCycle() { try { - TimePageLink pageLink = new TimePageLink(ctx.getEdgeEventStorageSettings().getMaxReadRecordsCount(), 0, null, null, this.newStartTs, System.currentTimeMillis()); - PageData edgeEvents = ctx.getEdgeEventService().findEdgeEvents(edge.getTenantId(), edge.getId(), 0L, this.previousStartSeqId == 0 ? null : this.previousStartSeqId - 1, pageLink); + TimePageLink pageLink = new TimePageLink(ctx.getEdgeEventStorageSettings().getMaxReadRecordsCount(), 0, null, null, newStartTs, System.currentTimeMillis()); + PageData edgeEvents = ctx.getEdgeEventService().findEdgeEvents(edge.getTenantId(), edge.getId(), 0L, previousStartSeqId == 0 ? null : previousStartSeqId - 1, pageLink); return !edgeEvents.getData().isEmpty(); } catch (Exception e) { - log.error("[{}][{}][{}] Failed to execute isSeqIdStartedNewCycle", this.tenantId, edge.getId(), sessionId, e); + log.error("[{}][{}][{}] Failed to execute isSeqIdStartedNewCycle", tenantId, edge.getId(), sessionId, e); } return false; } private boolean isNewEdgeEventsAvailable() { try { - TimePageLink pageLink = new TimePageLink(ctx.getEdgeEventStorageSettings().getMaxReadRecordsCount(), 0, null, null, this.newStartTs, System.currentTimeMillis()); - PageData edgeEvents = ctx.getEdgeEventService().findEdgeEvents(edge.getTenantId(), edge.getId(), this.newStartSeqId, null, pageLink); - return !edgeEvents.getData().isEmpty(); + TimePageLink pageLink = new TimePageLink(ctx.getEdgeEventStorageSettings().getMaxReadRecordsCount(), 0, null, null, newStartTs, System.currentTimeMillis()); + PageData edgeEvents = ctx.getEdgeEventService().findEdgeEvents(edge.getTenantId(), edge.getId(), newStartSeqId, null, pageLink); + return !edgeEvents.getData().isEmpty() || !highPriorityQueue.isEmpty(); } catch (Exception e) { - log.error("[{}][{}][{}] Failed to execute isNewEdgeEventsAvailable", this.tenantId, edge.getId(), sessionId, e); + log.error("[{}][{}][{}] Failed to execute isNewEdgeEventsAvailable", tenantId, edge.getId(), sessionId, e); } return false; } @@ -659,108 +693,135 @@ public final class EdgeGrpcSession implements Closeable { startSeqId = edgeEvents.getData().get(0).getSeqId() - 1; } } catch (Exception e) { - log.error("[{}][{}][{}] Failed to execute findStartSeqIdFromOldestEventIfAny", this.tenantId, edge.getId(), sessionId, e); + log.error("[{}][{}][{}] Failed to execute findStartSeqIdFromOldestEventIfAny", tenantId, edge.getId(), sessionId, e); } return startSeqId; } private ListenableFuture> updateQueueStartTsAndSeqId(Pair pair) { - this.newStartTs = pair.getFirst(); - this.newStartSeqId = pair.getSecond(); - log.trace("[{}] updateQueueStartTsAndSeqId [{}][{}][{}]", this.sessionId, edge.getId(), this.newStartTs, this.newStartSeqId); + newStartTs = pair.getFirst(); + newStartSeqId = pair.getSecond(); + log.trace("[{}] updateQueueStartTsAndSeqId [{}][{}][{}]", sessionId, edge.getId(), newStartTs, newStartSeqId); List attributes = Arrays.asList( - new BaseAttributeKvEntry(new LongDataEntry(QUEUE_START_TS_ATTR_KEY, this.newStartTs), System.currentTimeMillis()), - new BaseAttributeKvEntry(new LongDataEntry(QUEUE_START_SEQ_ID_ATTR_KEY, this.newStartSeqId), System.currentTimeMillis())); + new BaseAttributeKvEntry(new LongDataEntry(QUEUE_START_TS_ATTR_KEY, newStartTs), System.currentTimeMillis()), + new BaseAttributeKvEntry(new LongDataEntry(QUEUE_START_SEQ_ID_ATTR_KEY, newStartSeqId), System.currentTimeMillis())); return ctx.getAttributesService().save(edge.getTenantId(), edge.getId(), AttributeScope.SERVER_SCOPE, attributes); } - private DownlinkMsg convertEntityEventToDownlink(EdgeEvent edgeEvent) { - log.trace("[{}] Executing convertEntityEventToDownlink, edgeEvent [{}], action [{}]", this.tenantId, edgeEvent, edgeEvent.getAction()); - switch (edgeEvent.getType()) { - case EDGE: - return ctx.getEdgeProcessor().convertEdgeEventToDownlink(edgeEvent); - case DEVICE: - return ctx.getDeviceProcessor().convertDeviceEventToDownlink(edgeEvent, this.edge.getId(), this.edgeVersion); - case DEVICE_PROFILE: - return ctx.getDeviceProfileProcessor().convertDeviceProfileEventToDownlink(edgeEvent, this.edge.getId(), this.edgeVersion); - case ASSET_PROFILE: - return ctx.getAssetProfileProcessor().convertAssetProfileEventToDownlink(edgeEvent, this.edge.getId(), this.edgeVersion); - case ASSET: - return ctx.getAssetProcessor().convertAssetEventToDownlink(edgeEvent, this.edge.getId(), this.edgeVersion); - case ENTITY_VIEW: - return ctx.getEntityViewProcessor().convertEntityViewEventToDownlink(edgeEvent, this.edgeVersion); - case DASHBOARD: - return ctx.getDashboardProcessor().convertDashboardEventToDownlink(edgeEvent, this.edgeVersion); - case CUSTOMER: - return ctx.getCustomerProcessor().convertCustomerEventToDownlink(edgeEvent, this.edgeVersion); - case RULE_CHAIN: - return ctx.getRuleChainProcessor().convertRuleChainEventToDownlink(edgeEvent, this.edgeVersion); - case RULE_CHAIN_METADATA: - return ctx.getRuleChainProcessor().convertRuleChainMetadataEventToDownlink(edgeEvent, this.edgeVersion); - case ALARM: - return ctx.getAlarmProcessor().convertAlarmEventToDownlink(edgeEvent, this.edgeVersion); - case ALARM_COMMENT: - return ctx.getAlarmProcessor().convertAlarmCommentEventToDownlink(edgeEvent, this.edgeVersion); - case USER: - return ctx.getUserProcessor().convertUserEventToDownlink(edgeEvent, this.edgeVersion); - case RELATION: - return ctx.getRelationProcessor().convertRelationEventToDownlink(edgeEvent, this.edgeVersion); - case WIDGETS_BUNDLE: - return ctx.getWidgetBundleProcessor().convertWidgetsBundleEventToDownlink(edgeEvent, this.edgeVersion); - case WIDGET_TYPE: - return ctx.getWidgetTypeProcessor().convertWidgetTypeEventToDownlink(edgeEvent, this.edgeVersion); - case ADMIN_SETTINGS: - return ctx.getAdminSettingsProcessor().convertAdminSettingsEventToDownlink(edgeEvent, this.edgeVersion); - case OTA_PACKAGE: - return ctx.getOtaPackageProcessor().convertOtaPackageEventToDownlink(edgeEvent, this.edgeVersion); - case TB_RESOURCE: - return ctx.getResourceProcessor().convertResourceEventToDownlink(edgeEvent, this.edgeVersion); - case QUEUE: - return ctx.getQueueProcessor().convertQueueEventToDownlink(edgeEvent, this.edgeVersion); - case TENANT: - return ctx.getTenantProcessor().convertTenantEventToDownlink(edgeEvent, this.edgeVersion); - case TENANT_PROFILE: - return ctx.getTenantProfileProcessor().convertTenantProfileEventToDownlink(edgeEvent, this.edgeVersion); - case NOTIFICATION_RULE: - return ctx.getNotificationEdgeProcessor().convertNotificationRuleToDownlink(edgeEvent); - case NOTIFICATION_TARGET: - return ctx.getNotificationEdgeProcessor().convertNotificationTargetToDownlink(edgeEvent); - case NOTIFICATION_TEMPLATE: - return ctx.getNotificationEdgeProcessor().convertNotificationTemplateToDownlink(edgeEvent); - case OAUTH2_CLIENT: - return ctx.getOAuth2EdgeProcessor().convertOAuth2ClientEventToDownlink(edgeEvent, this.edgeVersion); - case DOMAIN: - return ctx.getOAuth2EdgeProcessor().convertOAuth2DomainEventToDownlink(edgeEvent, this.edgeVersion); - default: - log.warn("[{}] Unsupported edge event type [{}]", this.tenantId, edgeEvent); - return null; + protected ListenableFuture> startProcessingEdgeEvents(EdgeEventFetcher fetcher) { + SettableFuture> result = SettableFuture.create(); + PageLink pageLink = fetcher.getPageLink(ctx.getEdgeEventStorageSettings().getMaxReadRecordsCount()); + processEdgeEvents(fetcher, pageLink, result); + return result; + } + + private void markSyncCompletedSendEdgeEventUpdate() { + syncCompleted = true; + ctx.getClusterService().onEdgeEventUpdate(new EdgeEventUpdateMsg(edge.getTenantId(), edge.getId())); + } + + private void stopCurrentSendDownlinkMsgsTask(Boolean isInterrupted) { + if (sessionState.getSendDownlinkMsgsFuture() != null && !sessionState.getSendDownlinkMsgsFuture().isDone()) { + sessionState.getSendDownlinkMsgsFuture().set(isInterrupted); + } + if (sessionState.getScheduledSendDownlinkTask() != null) { + sessionState.getScheduledSendDownlinkTask().cancel(true); } } - private ListenableFuture> processUplinkMsg(UplinkMsg uplinkMsg) { + private void sendDownlinkMsg(ResponseMsg downlinkMsg) { + if (downlinkMsg.getDownlinkMsg().getWidgetTypeUpdateMsgCount() > 0) { + log.trace("[{}][{}] Sending downlink widgetTypeUpdateMsg, downlinkMsgId = {}", tenantId, sessionId, downlinkMsg.getDownlinkMsg().getDownlinkMsgId()); + } else { + log.trace("[{}][{}] Sending downlink msg [{}]", tenantId, sessionId, downlinkMsg); + } + if (isConnected()) { + downlinkMsgLock.lock(); + try { + outputStream.onNext(downlinkMsg); + } catch (Exception e) { + log.error("[{}][{}] Failed to send downlink message [{}]", tenantId, sessionId, downlinkMsg, e); + connected = false; + sessionCloseListener.accept(edge, sessionId); + } finally { + downlinkMsgLock.unlock(); + } + log.trace("[{}][{}] Response msg successfully sent. downlinkMsgId = {}", tenantId, sessionId, downlinkMsg.getDownlinkMsg().getDownlinkMsgId()); + } + } + + protected DownlinkMsg convertEntityEventToDownlink(EdgeEvent edgeEvent) { + log.trace("[{}] Executing convertEntityEventToDownlink, edgeEvent [{}], action [{}]", edgeEvent.getTenantId(), edgeEvent, edgeEvent.getAction()); + return switch (edgeEvent.getType()) { + case EDGE -> ctx.getEdgeProcessor().convertEdgeEventToDownlink(edgeEvent); + case DEVICE -> ctx.getDeviceProcessor().convertDeviceEventToDownlink(edgeEvent, edgeVersion); + case DEVICE_PROFILE -> ctx.getDeviceProfileProcessor().convertDeviceProfileEventToDownlink(edgeEvent, edgeVersion); + case ASSET_PROFILE -> ctx.getAssetProfileProcessor().convertAssetProfileEventToDownlink(edgeEvent, edgeVersion); + case ASSET -> ctx.getAssetProcessor().convertAssetEventToDownlink(edgeEvent, edgeVersion); + case ENTITY_VIEW -> ctx.getEntityViewProcessor().convertEntityViewEventToDownlink(edgeEvent, edgeVersion); + case DASHBOARD -> ctx.getDashboardProcessor().convertDashboardEventToDownlink(edgeEvent, edgeVersion); + case CUSTOMER -> ctx.getCustomerProcessor().convertCustomerEventToDownlink(edgeEvent, edgeVersion); + case RULE_CHAIN -> ctx.getRuleChainProcessor().convertRuleChainEventToDownlink(edgeEvent, edgeVersion); + case RULE_CHAIN_METADATA -> ctx.getRuleChainProcessor().convertRuleChainMetadataEventToDownlink(edgeEvent, edgeVersion); + case ALARM -> ctx.getAlarmProcessor().convertAlarmEventToDownlink(edgeEvent, edgeVersion); + case ALARM_COMMENT -> ctx.getAlarmProcessor().convertAlarmCommentEventToDownlink(edgeEvent, edgeVersion); + case USER -> ctx.getUserProcessor().convertUserEventToDownlink(edgeEvent, edgeVersion); + case RELATION -> ctx.getRelationProcessor().convertRelationEventToDownlink(edgeEvent, edgeVersion); + case WIDGETS_BUNDLE -> ctx.getWidgetBundleProcessor().convertWidgetsBundleEventToDownlink(edgeEvent, edgeVersion); + case WIDGET_TYPE -> ctx.getWidgetTypeProcessor().convertWidgetTypeEventToDownlink(edgeEvent, edgeVersion); + case ADMIN_SETTINGS -> ctx.getAdminSettingsProcessor().convertAdminSettingsEventToDownlink(edgeEvent, edgeVersion); + case OTA_PACKAGE -> ctx.getOtaPackageProcessor().convertOtaPackageEventToDownlink(edgeEvent, edgeVersion); + case TB_RESOURCE -> ctx.getResourceProcessor().convertResourceEventToDownlink(edgeEvent, edgeVersion); + case QUEUE -> ctx.getQueueProcessor().convertQueueEventToDownlink(edgeEvent, edgeVersion); + case TENANT -> ctx.getTenantProcessor().convertTenantEventToDownlink(edgeEvent, edgeVersion); + case TENANT_PROFILE -> ctx.getTenantProfileProcessor().convertTenantProfileEventToDownlink(edgeEvent, edgeVersion); + case NOTIFICATION_RULE -> ctx.getNotificationEdgeProcessor().convertNotificationRuleToDownlink(edgeEvent); + case NOTIFICATION_TARGET -> ctx.getNotificationEdgeProcessor().convertNotificationTargetToDownlink(edgeEvent); + case NOTIFICATION_TEMPLATE -> ctx.getNotificationEdgeProcessor().convertNotificationTemplateToDownlink(edgeEvent); + case OAUTH2_CLIENT -> ctx.getOAuth2EdgeProcessor().convertOAuth2ClientEventToDownlink(edgeEvent, edgeVersion); + case DOMAIN -> ctx.getOAuth2EdgeProcessor().convertOAuth2DomainEventToDownlink(edgeEvent, edgeVersion); + default -> { + log.warn("[{}] Unsupported edge event type [{}]", edgeEvent.getTenantId(), edgeEvent); + yield null; + } + }; + } + + public void addEventToHighPriorityQueue(EdgeEvent edgeEvent) { + while (highPriorityQueue.size() > maxHighPriorityQueueSizePerSession) { + EdgeEvent oldestHighPriority = highPriorityQueue.poll(); + if (oldestHighPriority != null) { + log.warn("[{}][{}][{}] High priority queue is full. Removing oldest high priority event from queue {}", + tenantId, edge.getId(), sessionId, oldestHighPriority); + } + } + highPriorityQueue.add(edgeEvent); + } + + protected ListenableFuture> processUplinkMsg(UplinkMsg uplinkMsg) { List> result = new ArrayList<>(); try { if (uplinkMsg.getDeviceProfileUpdateMsgCount() > 0) { for (DeviceProfileUpdateMsg deviceProfileUpdateMsg : uplinkMsg.getDeviceProfileUpdateMsgList()) { - result.add(((DeviceProfileProcessor) ctx.getDeviceProfileEdgeProcessorFactory().getProcessorByEdgeVersion(this.edgeVersion)) + result.add(((DeviceProfileProcessor) ctx.getDeviceProfileEdgeProcessorFactory().getProcessorByEdgeVersion(edgeVersion)) .processDeviceProfileMsgFromEdge(edge.getTenantId(), edge, deviceProfileUpdateMsg)); } } if (uplinkMsg.getDeviceUpdateMsgCount() > 0) { for (DeviceUpdateMsg deviceUpdateMsg : uplinkMsg.getDeviceUpdateMsgList()) { - result.add(((DeviceProcessor) ctx.getDeviceEdgeProcessorFactory().getProcessorByEdgeVersion(this.edgeVersion)) + result.add(((DeviceProcessor) ctx.getDeviceEdgeProcessorFactory().getProcessorByEdgeVersion(edgeVersion)) .processDeviceMsgFromEdge(edge.getTenantId(), edge, deviceUpdateMsg)); } } if (uplinkMsg.getDeviceCredentialsUpdateMsgCount() > 0) { for (DeviceCredentialsUpdateMsg deviceCredentialsUpdateMsg : uplinkMsg.getDeviceCredentialsUpdateMsgList()) { - result.add(((DeviceProcessor) ctx.getDeviceEdgeProcessorFactory().getProcessorByEdgeVersion(this.edgeVersion)) + result.add(((DeviceProcessor) ctx.getDeviceEdgeProcessorFactory().getProcessorByEdgeVersion(edgeVersion)) .processDeviceCredentialsMsgFromEdge(edge.getTenantId(), edge.getId(), deviceCredentialsUpdateMsg)); } } if (uplinkMsg.getAssetProfileUpdateMsgCount() > 0) { for (AssetProfileUpdateMsg assetProfileUpdateMsg : uplinkMsg.getAssetProfileUpdateMsgList()) { - result.add(((AssetProfileProcessor) ctx.getAssetProfileEdgeProcessorFactory().getProcessorByEdgeVersion(this.edgeVersion)) + result.add(((AssetProfileProcessor) ctx.getAssetProfileEdgeProcessorFactory().getProcessorByEdgeVersion(edgeVersion)) .processAssetProfileMsgFromEdge(edge.getTenantId(), edge, assetProfileUpdateMsg)); } } @@ -772,7 +833,7 @@ public final class EdgeGrpcSession implements Closeable { } if (uplinkMsg.getEntityViewUpdateMsgCount() > 0) { for (EntityViewUpdateMsg entityViewUpdateMsg : uplinkMsg.getEntityViewUpdateMsgList()) { - result.add(((EntityViewProcessor) ctx.getEntityViewProcessorFactory().getProcessorByEdgeVersion(this.edgeVersion)) + result.add(((EntityViewProcessor) ctx.getEntityViewProcessorFactory().getProcessorByEdgeVersion(edgeVersion)) .processEntityViewMsgFromEdge(edge.getTenantId(), edge, entityViewUpdateMsg)); } } @@ -783,31 +844,31 @@ public final class EdgeGrpcSession implements Closeable { } if (uplinkMsg.getAlarmUpdateMsgCount() > 0) { for (AlarmUpdateMsg alarmUpdateMsg : uplinkMsg.getAlarmUpdateMsgList()) { - result.add(((AlarmProcessor) ctx.getAlarmEdgeProcessorFactory().getProcessorByEdgeVersion(this.edgeVersion)) + result.add(((AlarmProcessor) ctx.getAlarmEdgeProcessorFactory().getProcessorByEdgeVersion(edgeVersion)) .processAlarmMsgFromEdge(edge.getTenantId(), edge.getId(), alarmUpdateMsg)); } } if (uplinkMsg.getAlarmCommentUpdateMsgCount() > 0) { for (AlarmCommentUpdateMsg alarmCommentUpdateMsg : uplinkMsg.getAlarmCommentUpdateMsgList()) { - result.add(((AlarmProcessor) ctx.getAlarmEdgeProcessorFactory().getProcessorByEdgeVersion(this.edgeVersion)) + result.add(((AlarmProcessor) ctx.getAlarmEdgeProcessorFactory().getProcessorByEdgeVersion(edgeVersion)) .processAlarmCommentMsgFromEdge(edge.getTenantId(), edge.getId(), alarmCommentUpdateMsg)); } } if (uplinkMsg.getRelationUpdateMsgCount() > 0) { for (RelationUpdateMsg relationUpdateMsg : uplinkMsg.getRelationUpdateMsgList()) { - result.add(((RelationProcessor) ctx.getRelationEdgeProcessorFactory().getProcessorByEdgeVersion(this.edgeVersion)) + result.add(((RelationProcessor) ctx.getRelationEdgeProcessorFactory().getProcessorByEdgeVersion(edgeVersion)) .processRelationMsgFromEdge(edge.getTenantId(), edge, relationUpdateMsg)); } } if (uplinkMsg.getDashboardUpdateMsgCount() > 0) { for (DashboardUpdateMsg dashboardUpdateMsg : uplinkMsg.getDashboardUpdateMsgList()) { - result.add(((DashboardProcessor) ctx.getDashboardEdgeProcessorFactory().getProcessorByEdgeVersion(this.edgeVersion)) + result.add(((DashboardProcessor) ctx.getDashboardEdgeProcessorFactory().getProcessorByEdgeVersion(edgeVersion)) .processDashboardMsgFromEdge(edge.getTenantId(), edge, dashboardUpdateMsg)); } } if (uplinkMsg.getResourceUpdateMsgCount() > 0) { for (ResourceUpdateMsg resourceUpdateMsg : uplinkMsg.getResourceUpdateMsgList()) { - result.add(((ResourceProcessor) ctx.getResourceEdgeProcessorFactory().getProcessorByEdgeVersion(this.edgeVersion)) + result.add(((ResourceProcessor) ctx.getResourceEdgeProcessorFactory().getProcessorByEdgeVersion(edgeVersion)) .processResourceMsgFromEdge(edge.getTenantId(), edge, resourceUpdateMsg)); } } @@ -838,7 +899,7 @@ public final class EdgeGrpcSession implements Closeable { } if (uplinkMsg.getDeviceRpcCallMsgCount() > 0) { for (DeviceRpcCallMsg deviceRpcCallMsg : uplinkMsg.getDeviceRpcCallMsgList()) { - result.add(((DeviceProcessor) ctx.getDeviceEdgeProcessorFactory().getProcessorByEdgeVersion(this.edgeVersion)) + result.add(((DeviceProcessor) ctx.getDeviceEdgeProcessorFactory().getProcessorByEdgeVersion(edgeVersion)) .processDeviceRpcCallFromEdge(edge.getTenantId(), edge, deviceRpcCallMsg)); } } @@ -854,104 +915,27 @@ public final class EdgeGrpcSession implements Closeable { } } catch (Exception e) { String failureMsg = String.format("Can't process uplink msg [%s] from edge", uplinkMsg); - log.error("[{}][{}] Can't process uplink msg [{}]", this.tenantId, this.sessionId, uplinkMsg, e); - ctx.getNotificationRuleProcessor().process(EdgeCommunicationFailureTrigger.builder().tenantId(tenantId).edgeId(edge.getId()) + log.error("[{}][{}] Can't process uplink msg [{}]", edge.getTenantId(), sessionId, uplinkMsg, e); + ctx.getNotificationRuleProcessor().process(EdgeCommunicationFailureTrigger.builder().tenantId(edge.getTenantId()).edgeId(edge.getId()) .customerId(edge.getCustomerId()).edgeName(edge.getName()).failureMsg(failureMsg).error(e.getMessage()).build()); return Futures.immediateFailedFuture(e); } return Futures.allAsList(result); } - private ConnectResponseMsg processConnect(ConnectRequestMsg request) { - log.trace("[{}] processConnect [{}]", this.sessionId, request); - Optional optional = ctx.getEdgeService().findEdgeByRoutingKey(TenantId.SYS_TENANT_ID, request.getEdgeRoutingKey()); - if (optional.isPresent()) { - edge = optional.get(); - this.tenantId = edge.getTenantId(); - try { - if (edge.getSecret().equals(request.getEdgeSecret())) { - sessionOpenListener.accept(edge.getId(), this); - this.edgeVersion = request.getEdgeVersion(); - processSaveEdgeVersionAsAttribute(request.getEdgeVersion().name()); - return ConnectResponseMsg.newBuilder() - .setResponseCode(ConnectResponseCode.ACCEPTED) - .setErrorMsg("") - .setConfiguration(ctx.getEdgeMsgConstructor().constructEdgeConfiguration(edge)) - .setMaxInboundMessageSize(maxInboundMessageSize) - .build(); - } - String error = "Failed to validate the edge!"; - String failureMsg = String.format("%s Provided request secret: %s", error, request.getEdgeSecret()); - ctx.getNotificationRuleProcessor().process(EdgeCommunicationFailureTrigger.builder().tenantId(tenantId).edgeId(edge.getId()) - .customerId(edge.getCustomerId()).edgeName(edge.getName()).failureMsg(failureMsg).error(error).build()); - return ConnectResponseMsg.newBuilder() - .setResponseCode(ConnectResponseCode.BAD_CREDENTIALS) - .setErrorMsg(failureMsg) - .setConfiguration(EdgeConfiguration.getDefaultInstance()).build(); - } catch (Exception e) { - String failureMsg = "Failed to process edge connection!"; - ctx.getNotificationRuleProcessor().process(EdgeCommunicationFailureTrigger.builder().tenantId(tenantId).edgeId(edge.getId()) - .customerId(edge.getCustomerId()).edgeName(edge.getName()).failureMsg(failureMsg).error(e.getMessage()).build()); - log.error(failureMsg, e); - return ConnectResponseMsg.newBuilder() - .setResponseCode(ConnectResponseCode.SERVER_UNAVAILABLE) - .setErrorMsg(failureMsg) - .setConfiguration(EdgeConfiguration.getDefaultInstance()).build(); - } - } - return ConnectResponseMsg.newBuilder() - .setResponseCode(ConnectResponseCode.BAD_CREDENTIALS) - .setErrorMsg("Failed to find the edge! Routing key: " + request.getEdgeRoutingKey()) - .setConfiguration(EdgeConfiguration.getDefaultInstance()).build(); - } + protected void destroy() {} - private void processSaveEdgeVersionAsAttribute(String edgeVersion) { - AttributeKvEntry attributeKvEntry = new BaseAttributeKvEntry(new StringDataEntry(DataConstants.EDGE_VERSION_ATTR_KEY, edgeVersion), System.currentTimeMillis()); - ctx.getAttributesService().save(this.tenantId, this.edge.getId(), AttributeScope.SERVER_SCOPE, attributeKvEntry); - } + protected void cleanUp() {} @Override public void close() { - log.debug("[{}][{}] Closing session", this.tenantId, sessionId); + log.debug("[{}][{}] Closing session", tenantId, sessionId); connected = false; try { outputStream.onCompleted(); } catch (Exception e) { - log.debug("[{}][{}] Failed to close output stream: {}", this.tenantId, sessionId, e.getMessage()); + log.debug("[{}][{}] Failed to close output stream: {}", tenantId, sessionId, e.getMessage()); } } - private void interruptPreviousSendDownlinkMsgsTask() { - if (sessionState.getSendDownlinkMsgsFuture() != null && !sessionState.getSendDownlinkMsgsFuture().isDone() - || sessionState.getScheduledSendDownlinkTask() != null && !sessionState.getScheduledSendDownlinkTask().isCancelled()) { - log.debug("[{}][{}][{}] Previous send downlink future was not properly completed, stopping it now!", this.tenantId, edge.getId(), this.sessionId); - stopCurrentSendDownlinkMsgsTask(true); - } - } - - private void interruptGeneralProcessingOnSync() { - log.debug("[{}][{}][{}] Sync process started. General processing interrupted!", this.tenantId, edge.getId(), this.sessionId); - stopCurrentSendDownlinkMsgsTask(true); - } - - public void stopCurrentSendDownlinkMsgsTask(Boolean isInterrupted) { - if (sessionState.getSendDownlinkMsgsFuture() != null && !sessionState.getSendDownlinkMsgsFuture().isDone()) { - sessionState.getSendDownlinkMsgsFuture().set(isInterrupted); - } - if (sessionState.getScheduledSendDownlinkTask() != null) { - sessionState.getScheduledSendDownlinkTask().cancel(true); - } - } - - public void addEventToHighPriorityQueue(EdgeEvent edgeEvent) { - while (highPriorityQueue.size() > maxHighPriorityQueueSizePerSession) { - EdgeEvent oldestHighPriority = highPriorityQueue.poll(); - if (oldestHighPriority != null) { - log.warn("[{}][{}][{}] High priority queue is full. Removing oldest high priority event from queue {}", - this.tenantId, edge.getId(), this.sessionId, oldestHighPriority); - } - } - highPriorityQueue.add(edgeEvent); - } - } 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 new file mode 100644 index 0000000000..454645ca65 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/KafkaEdgeEventService.java @@ -0,0 +1,55 @@ +/** + * Copyright © 2016-2024 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.edge.rpc; + +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.edge.EdgeEvent; +import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; +import org.thingsboard.server.common.util.ProtoUtils; +import org.thingsboard.server.dao.edge.BaseEdgeEventService; +import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeEventNotificationMsg; +import org.thingsboard.server.queue.common.TbProtoQueueMsg; +import org.thingsboard.server.queue.discovery.TopicService; +import org.thingsboard.server.queue.provider.TbQueueProducerProvider; + +import java.util.UUID; + +@Slf4j +@Service +@RequiredArgsConstructor +@ConditionalOnExpression("'${queue.type:null}'=='kafka'") +public class KafkaEdgeEventService extends BaseEdgeEventService { + + private final TopicService topicService; + private final TbQueueProducerProvider producerProvider; + + @Override + public ListenableFuture saveAsync(EdgeEvent edgeEvent) { + validateEdgeEvent(edgeEvent); + + 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); + + return Futures.immediateFuture(null); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/KafkaEdgeGrpcSession.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/KafkaEdgeGrpcSession.java new file mode 100644 index 0000000000..9cb3f2dffc --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/KafkaEdgeGrpcSession.java @@ -0,0 +1,146 @@ +/** + * Copyright © 2016-2024 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.edge.rpc; + +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import io.grpc.stub.StreamObserver; +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.common.util.ThingsBoardThreadFactory; +import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.common.data.edge.EdgeEvent; +import org.thingsboard.server.common.data.id.EdgeId; +import org.thingsboard.server.common.util.ProtoUtils; +import org.thingsboard.server.gen.edge.v1.DownlinkMsg; +import org.thingsboard.server.gen.edge.v1.ResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeEventNotificationMsg; +import org.thingsboard.server.queue.TbQueueConsumer; +import org.thingsboard.server.queue.common.TbProtoQueueMsg; +import org.thingsboard.server.queue.common.consumer.QueueConsumerManager; +import org.thingsboard.server.queue.discovery.TopicService; +import org.thingsboard.server.queue.kafka.TbKafkaAdmin; +import org.thingsboard.server.queue.kafka.TbKafkaSettings; +import org.thingsboard.server.queue.kafka.TbKafkaTopicConfigs; +import org.thingsboard.server.queue.provider.TbCoreQueueFactory; +import org.thingsboard.server.service.edge.EdgeContextComponent; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.function.BiConsumer; + +@Slf4j +public class KafkaEdgeGrpcSession extends EdgeGrpcSession { + + private final TopicService topicService; + private final TbCoreQueueFactory tbCoreQueueFactory; + + private final TbKafkaSettings kafkaSettings; + private final TbKafkaTopicConfigs kafkaTopicConfigs; + + private volatile boolean isHighPriorityProcessing; + + private QueueConsumerManager> consumer; + + private ExecutorService consumerExecutor; + + public KafkaEdgeGrpcSession(EdgeContextComponent ctx, TopicService topicService, TbCoreQueueFactory tbCoreQueueFactory, + TbKafkaSettings kafkaSettings, TbKafkaTopicConfigs kafkaTopicConfigs, StreamObserver outputStream, + BiConsumer sessionOpenListener, BiConsumer sessionCloseListener, + ScheduledExecutorService sendDownlinkExecutorService, int maxInboundMessageSize, int maxHighPriorityQueueSizePerSession) { + super(ctx, outputStream, sessionOpenListener, sessionCloseListener, sendDownlinkExecutorService, maxInboundMessageSize, maxHighPriorityQueueSizePerSession); + this.topicService = topicService; + this.tbCoreQueueFactory = tbCoreQueueFactory; + this.kafkaSettings = kafkaSettings; + this.kafkaTopicConfigs = kafkaTopicConfigs; + } + + private void processMsgs(List> msgs, TbQueueConsumer> consumer) { + log.trace("[{}][{}] starting processing edge events", tenantId, sessionId); + if (isConnected() && isSyncCompleted() && !isHighPriorityProcessing) { + List edgeEvents = new ArrayList<>(); + for (TbProtoQueueMsg msg : msgs) { + EdgeEvent edgeEvent = ProtoUtils.fromProto(msg.getValue().getEdgeEventMsg()); + edgeEvents.add(edgeEvent); + } + List downlinkMsgsPack = convertToDownlinkMsgsPack(edgeEvents); + try { + boolean isInterrupted = sendDownlinkMsgsPack(downlinkMsgsPack).get(); + if (isInterrupted) { + log.debug("[{}][{}][{}] Send downlink messages task was interrupted", tenantId, edge.getId(), sessionId); + } else { + consumer.commit(); + } + } catch (Exception e) { + log.error("[{}] Failed to process all downlink messages", sessionId, e); + } + } else { + try { + Thread.sleep(ctx.getEdgeEventStorageSettings().getNoRecordsSleepInterval()); + } catch (InterruptedException interruptedException) { + log.trace("Failed to wait until the server has capacity to handle new requests", interruptedException); + } + log.trace("[{}][{}] edge is not connected or sync is not completed. Skipping iteration", tenantId, sessionId); + } + } + + @Override + public ListenableFuture migrateEdgeEvents() throws Exception { + return super.processEdgeEvents(); + } + + @Override + public ListenableFuture processEdgeEvents() { + if (consumer == null) { + this.consumerExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("edge-event-consumer")); + this.consumer = QueueConsumerManager.>builder() + .name("TB Edge events") + .msgPackProcessor(this::processMsgs) + .pollInterval(ctx.getEdgeEventStorageSettings().getNoRecordsSleepInterval()) + .consumerCreator(() -> tbCoreQueueFactory.createEdgeEventMsgConsumer(tenantId, edge.getId())) + .consumerExecutor(consumerExecutor) + .threadPrefix("edge-events") + .build(); + consumer.subscribe(); + consumer.launch(); + } + return Futures.immediateFuture(Boolean.FALSE); + } + + @Override + public void processHighPriorityEvents() { + isHighPriorityProcessing = true; + super.processHighPriorityEvents(); + isHighPriorityProcessing = false; + } + + @Override + public void destroy() { + consumer.stop(); + consumerExecutor.shutdown(); + } + + @Override + public void cleanUp() { + String topic = topicService.buildEdgeEventNotificationsTopicPartitionInfo(tenantId, edge.getId()).getTopic(); + TbKafkaAdmin kafkaAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getEdgeEventConfigs()); + kafkaAdmin.deleteTopic(topic); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/PostgresEdgeGrpcSession.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/PostgresEdgeGrpcSession.java new file mode 100644 index 0000000000..3f80e625b5 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/PostgresEdgeGrpcSession.java @@ -0,0 +1,46 @@ +/** + * Copyright © 2016-2024 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.edge.rpc; + +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import io.grpc.stub.StreamObserver; +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.common.data.id.EdgeId; +import org.thingsboard.server.gen.edge.v1.ResponseMsg; +import org.thingsboard.server.service.edge.EdgeContextComponent; + +import java.util.UUID; +import java.util.concurrent.ScheduledExecutorService; +import java.util.function.BiConsumer; + +@Slf4j +public class PostgresEdgeGrpcSession extends EdgeGrpcSession { + + PostgresEdgeGrpcSession(EdgeContextComponent ctx, StreamObserver outputStream, + BiConsumer sessionOpenListener, + BiConsumer sessionCloseListener, ScheduledExecutorService sendDownlinkExecutorService, + int maxInboundMessageSize, int maxHighPriorityQueueSizePerSession) { + super(ctx, outputStream, sessionOpenListener, sessionCloseListener, sendDownlinkExecutorService, maxInboundMessageSize, maxHighPriorityQueueSizePerSession); + } + + @Override + public ListenableFuture migrateEdgeEvents() { + return Futures.immediateFuture(Boolean.FALSE); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java index 3c18e02324..e448fbd937 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java @@ -20,19 +20,10 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Lazy; import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.AttributeScope; -import org.thingsboard.server.common.data.Dashboard; -import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.EntityView; -import org.thingsboard.server.common.data.TbResource; -import org.thingsboard.server.common.data.asset.Asset; -import org.thingsboard.server.common.data.asset.AssetProfile; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEventActionType; @@ -59,72 +50,15 @@ import org.thingsboard.server.common.data.rule.RuleChainConnectionInfo; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; -import org.thingsboard.server.dao.alarm.AlarmCommentService; -import org.thingsboard.server.dao.alarm.AlarmService; -import org.thingsboard.server.dao.asset.AssetProfileService; -import org.thingsboard.server.dao.asset.AssetService; -import org.thingsboard.server.dao.attributes.AttributesService; -import org.thingsboard.server.dao.customer.CustomerService; -import org.thingsboard.server.dao.dashboard.DashboardService; -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.domain.DomainService; -import org.thingsboard.server.dao.edge.EdgeEventService; -import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.edge.EdgeSynchronizationManager; -import org.thingsboard.server.dao.entityview.EntityViewService; -import org.thingsboard.server.dao.notification.NotificationRuleService; -import org.thingsboard.server.dao.notification.NotificationTargetService; -import org.thingsboard.server.dao.notification.NotificationTemplateService; -import org.thingsboard.server.dao.oauth2.OAuth2ClientService; -import org.thingsboard.server.dao.ota.OtaPackageService; -import org.thingsboard.server.dao.queue.QueueService; -import org.thingsboard.server.dao.relation.RelationService; -import org.thingsboard.server.dao.resource.ResourceService; -import org.thingsboard.server.dao.rule.RuleChainService; -import org.thingsboard.server.dao.service.DataValidator; -import org.thingsboard.server.dao.tenant.TenantProfileService; -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.gen.edge.v1.EdgeVersion; +import org.thingsboard.server.dao.entity.EntityDaoRegistry; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.TbQueueMsgMetadata; -import org.thingsboard.server.queue.discovery.PartitionService; -import org.thingsboard.server.queue.provider.TbQueueProducerProvider; -import org.thingsboard.server.service.edge.rpc.constructor.alarm.AlarmMsgConstructorFactory; -import org.thingsboard.server.service.edge.rpc.constructor.asset.AssetMsgConstructorFactory; -import org.thingsboard.server.service.edge.rpc.constructor.customer.CustomerMsgConstructorFactory; -import org.thingsboard.server.service.edge.rpc.constructor.dashboard.DashboardMsgConstructorFactory; -import org.thingsboard.server.service.edge.rpc.constructor.device.DeviceMsgConstructorFactory; -import org.thingsboard.server.service.edge.rpc.constructor.edge.EdgeMsgConstructor; -import org.thingsboard.server.service.edge.rpc.constructor.entityview.EntityViewMsgConstructorFactory; -import org.thingsboard.server.service.edge.rpc.constructor.notification.NotificationMsgConstructor; -import org.thingsboard.server.service.edge.rpc.constructor.oauth2.OAuth2MsgConstructor; -import org.thingsboard.server.service.edge.rpc.constructor.ota.OtaPackageMsgConstructorFactory; -import org.thingsboard.server.service.edge.rpc.constructor.queue.QueueMsgConstructorFactory; -import org.thingsboard.server.service.edge.rpc.constructor.relation.RelationMsgConstructorFactory; -import org.thingsboard.server.service.edge.rpc.constructor.resource.ResourceMsgConstructorFactory; -import org.thingsboard.server.service.edge.rpc.constructor.rule.RuleChainMsgConstructorFactory; -import org.thingsboard.server.service.edge.rpc.constructor.settings.AdminSettingsMsgConstructorFactory; -import org.thingsboard.server.service.edge.rpc.constructor.telemetry.EntityDataMsgConstructor; -import org.thingsboard.server.service.edge.rpc.constructor.tenant.TenantMsgConstructorFactory; -import org.thingsboard.server.service.edge.rpc.constructor.user.UserMsgConstructorFactory; -import org.thingsboard.server.service.edge.rpc.constructor.widget.WidgetMsgConstructorFactory; -import org.thingsboard.server.service.edge.rpc.processor.alarm.AlarmEdgeProcessorFactory; -import org.thingsboard.server.service.edge.rpc.processor.asset.AssetEdgeProcessorFactory; -import org.thingsboard.server.service.edge.rpc.processor.entityview.EntityViewProcessorFactory; -import org.thingsboard.server.service.entitiy.TbLogEntityActionService; +import org.thingsboard.server.service.edge.EdgeContextComponent; import org.thingsboard.server.service.executors.DbCallbackExecutorService; -import org.thingsboard.server.service.profile.TbAssetProfileCache; -import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.state.DefaultDeviceStateService; -import org.thingsboard.server.service.state.DeviceStateService; -import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import java.util.ArrayList; import java.util.List; @@ -142,201 +76,10 @@ public abstract class BaseEdgeProcessor { protected static final Lock assetCreationLock = new ReentrantLock(); @Autowired - protected TelemetrySubscriptionService tsSubService; + protected EdgeContextComponent edgeCtx; @Autowired - protected TbLogEntityActionService logEntityActionService; - - @Autowired - protected RuleChainService ruleChainService; - - @Autowired - protected AlarmService alarmService; - - @Autowired - protected AlarmCommentService alarmCommentService; - - @Autowired - protected DeviceService deviceService; - - @Autowired - protected TbDeviceProfileCache deviceProfileCache; - - @Autowired - protected TbAssetProfileCache assetProfileCache; - - @Autowired - protected DashboardService dashboardService; - - @Autowired - protected AssetService assetService; - - @Autowired - protected EntityViewService entityViewService; - - @Autowired - protected TenantService tenantService; - - @Autowired - protected TenantProfileService tenantProfileService; - - @Autowired - protected EdgeService edgeService; - - @Autowired - protected CustomerService customerService; - - @Autowired - protected UserService userService; - - @Autowired - protected DeviceProfileService deviceProfileService; - - @Autowired - protected AssetProfileService assetProfileService; - - @Autowired - protected RelationService relationService; - - @Autowired - protected DeviceCredentialsService deviceCredentialsService; - - @Autowired - protected AttributesService attributesService; - - @Autowired - protected TbClusterService tbClusterService; - - @Autowired - protected DeviceStateService deviceStateService; - - @Autowired - protected EdgeEventService edgeEventService; - - @Autowired - protected WidgetsBundleService widgetsBundleService; - - @Autowired - protected WidgetTypeService widgetTypeService; - - @Autowired - protected OtaPackageService otaPackageService; - - @Autowired - protected QueueService queueService; - - @Autowired - protected PartitionService partitionService; - - @Autowired - protected ResourceService resourceService; - - @Autowired - protected NotificationRuleService notificationRuleService; - - @Autowired - protected NotificationTargetService notificationTargetService; - - @Autowired - protected NotificationTemplateService notificationTemplateService; - - @Autowired - protected OAuth2ClientService oAuth2ClientService; - - @Autowired - protected DomainService domainService; - - @Autowired - @Lazy - protected TbQueueProducerProvider producerProvider; - - @Autowired - protected DataValidator deviceValidator; - - @Autowired - protected DataValidator deviceProfileValidator; - - @Autowired - protected DataValidator assetValidator; - - @Autowired - protected DataValidator assetProfileValidator; - - @Autowired - protected DataValidator dashboardValidator; - - @Autowired - protected DataValidator entityViewValidator; - - @Autowired - protected DataValidator resourceValidator; - - @Autowired - protected EdgeMsgConstructor edgeMsgConstructor; - - @Autowired - protected EntityDataMsgConstructor entityDataMsgConstructor; - - @Autowired - protected NotificationMsgConstructor notificationMsgConstructor; - - @Autowired - protected OAuth2MsgConstructor oAuth2MsgConstructor; - - - @Autowired - protected RuleChainMsgConstructorFactory ruleChainMsgConstructorFactory; - - @Autowired - protected AlarmMsgConstructorFactory alarmMsgConstructorFactory; - - @Autowired - protected DeviceMsgConstructorFactory deviceMsgConstructorFactory; - - @Autowired - protected AssetMsgConstructorFactory assetMsgConstructorFactory; - - @Autowired - protected EntityViewMsgConstructorFactory entityViewMsgConstructorFactory; - - @Autowired - protected DashboardMsgConstructorFactory dashboardMsgConstructorFactory; - - @Autowired - protected RelationMsgConstructorFactory relationMsgConstructorFactory; - - @Autowired - protected UserMsgConstructorFactory userMsgConstructorFactory; - - @Autowired - protected CustomerMsgConstructorFactory customerMsgConstructorFactory; - - @Autowired - protected TenantMsgConstructorFactory tenantMsgConstructorFactory; - - @Autowired - protected WidgetMsgConstructorFactory widgetMsgConstructorFactory; - - @Autowired - protected AdminSettingsMsgConstructorFactory adminSettingsMsgConstructorFactory; - - @Autowired - protected OtaPackageMsgConstructorFactory otaPackageMsgConstructorFactory; - - @Autowired - protected QueueMsgConstructorFactory queueMsgConstructorFactory; - - @Autowired - protected ResourceMsgConstructorFactory resourceMsgConstructorFactory; - - @Autowired - protected AlarmEdgeProcessorFactory alarmEdgeProcessorFactory; - - @Autowired - protected AssetEdgeProcessorFactory assetEdgeProcessorFactory; - - @Autowired - protected EntityViewProcessorFactory entityViewProcessorFactory; + protected EntityDaoRegistry entityDaoRegistry; @Autowired protected EdgeSynchronizationManager edgeSynchronizationManager; @@ -351,7 +94,7 @@ public abstract class BaseEdgeProcessor { EntityId entityId, JsonNode body) { ListenableFuture> future = - attributesService.find(tenantId, edgeId, AttributeScope.SERVER_SCOPE, DefaultDeviceStateService.ACTIVITY_STATE); + edgeCtx.getAttributesService().find(tenantId, edgeId, AttributeScope.SERVER_SCOPE, DefaultDeviceStateService.ACTIVITY_STATE); return Futures.transformAsync(future, activeOpt -> { if (activeOpt.isEmpty()) { log.trace("Edge is not activated. Skipping event. tenantId [{}], edgeId [{}], type[{}], " + @@ -376,11 +119,12 @@ public abstract class BaseEdgeProcessor { private boolean doSaveIfEdgeIsOffline(EdgeEventType type, EdgeEventActionType action) { return switch (action) { - case TIMESERIES_UPDATED, ALARM_ACK, ALARM_CLEAR, ALARM_ASSIGNED, ALARM_UNASSIGNED, ADDED_COMMENT, UPDATED_COMMENT -> - true; + case TIMESERIES_UPDATED, ALARM_ACK, ALARM_CLEAR, ALARM_ASSIGNED, ALARM_UNASSIGNED, ADDED_COMMENT, + UPDATED_COMMENT -> true; default -> switch (type) { - case ALARM, ALARM_COMMENT, RULE_CHAIN, RULE_CHAIN_METADATA, USER, CUSTOMER, TENANT, TENANT_PROFILE, WIDGETS_BUNDLE, WIDGET_TYPE, - ADMIN_SETTINGS, OTA_PACKAGE, QUEUE, RELATION, NOTIFICATION_TEMPLATE, NOTIFICATION_TARGET, NOTIFICATION_RULE -> true; + case ALARM, ALARM_COMMENT, RULE_CHAIN, RULE_CHAIN_METADATA, USER, CUSTOMER, TENANT, TENANT_PROFILE, + WIDGETS_BUNDLE, WIDGET_TYPE, ADMIN_SETTINGS, OTA_PACKAGE, QUEUE, RELATION, NOTIFICATION_TEMPLATE, NOTIFICATION_TARGET, + NOTIFICATION_RULE -> true; default -> false; }; }; @@ -391,8 +135,7 @@ public abstract class BaseEdgeProcessor { tenantId, edgeId, type, action, entityId, body); EdgeEvent edgeEvent = EdgeUtils.constructEdgeEvent(tenantId, edgeId, type, action, entityId, body); - - return edgeEventService.saveAsync(edgeEvent); + return edgeCtx.getEdgeEventService().saveAsync(edgeEvent); } protected ListenableFuture processActionForAllEdges(TenantId tenantId, EdgeEventType type, @@ -400,7 +143,7 @@ public abstract class BaseEdgeProcessor { JsonNode body, EdgeId sourceEdgeId) { List> futures = new ArrayList<>(); if (TenantId.SYS_TENANT_ID.equals(tenantId)) { - PageDataIterable tenantIds = new PageDataIterable<>(link -> tenantService.findTenantsIds(link), 1024); + PageDataIterable tenantIds = new PageDataIterable<>(link -> edgeCtx.getTenantService().findTenantsIds(link), 1024); for (TenantId tenantId1 : tenantIds) { futures.addAll(processActionForAllEdgesByTenantId(tenantId1, type, actionType, entityId, body, sourceEdgeId)); } @@ -417,7 +160,7 @@ public abstract class BaseEdgeProcessor { JsonNode body, EdgeId sourceEdgeId) { List> futures = new ArrayList<>(); - PageDataIterable edges = new PageDataIterable<>(link -> edgeService.findEdgesByTenantId(tenantId, link), 1024); + PageDataIterable edges = new PageDataIterable<>(link -> edgeCtx.getEdgeService().findEdgesByTenantId(tenantId, link), 1024); for (Edge edge : edges) { if (!edge.getId().equals(sourceEdgeId)) { futures.add(saveEdgeEvent(tenantId, edge.getId(), type, actionType, entityId, body)); @@ -504,7 +247,7 @@ public abstract class BaseEdgeProcessor { EdgeEventActionType actionType, EdgeId sourceEdgeId) { List> futures = new ArrayList<>(); PageDataIterableByTenantIdEntityId edgeIds = - new PageDataIterableByTenantIdEntityId<>(edgeService::findRelatedEdgeIdsByEntityId, tenantId, entityId, RELATED_EDGES_CACHE_ITEMS); + new PageDataIterableByTenantIdEntityId<>(edgeCtx.getEdgeService()::findRelatedEdgeIdsByEntityId, tenantId, entityId, RELATED_EDGES_CACHE_ITEMS); for (EdgeId relatedEdgeId : edgeIds) { if (!relatedEdgeId.equals(sourceEdgeId)) { futures.add(saveEdgeEvent(tenantId, relatedEdgeId, type, actionType, entityId, null)); @@ -515,10 +258,10 @@ public abstract class BaseEdgeProcessor { private ListenableFuture updateDependentRuleChains(TenantId tenantId, RuleChainId processingRuleChainId, EdgeId edgeId) { List> futures = new ArrayList<>(); - PageDataIterable ruleChains = new PageDataIterable<>(link -> ruleChainService.findRuleChainsByTenantIdAndEdgeId(tenantId, edgeId, link), 1024); + PageDataIterable ruleChains = new PageDataIterable<>(link -> edgeCtx.getRuleChainService().findRuleChainsByTenantIdAndEdgeId(tenantId, edgeId, link), 1024); for (RuleChain ruleChain : ruleChains) { List connectionInfos = - ruleChainService.loadRuleChainMetaData(ruleChain.getTenantId(), ruleChain.getId()).getRuleChainConnections(); + edgeCtx.getRuleChainService().loadRuleChainMetaData(ruleChain.getTenantId(), ruleChain.getId()).getRuleChainConnections(); if (connectionInfos != null && !connectionInfos.isEmpty()) { for (RuleChainConnectionInfo connectionInfo : connectionInfos) { if (connectionInfo.getTargetRuleChainId().equals(processingRuleChainId)) { @@ -576,17 +319,7 @@ public abstract class BaseEdgeProcessor { } protected boolean isEntityExists(TenantId tenantId, EntityId entityId) { - return switch (entityId.getEntityType()) { - case TENANT -> tenantService.findTenantById(tenantId) != null; - case DEVICE -> deviceService.findDeviceById(tenantId, new DeviceId(entityId.getId())) != null; - case ASSET -> assetService.findAssetById(tenantId, new AssetId(entityId.getId())) != null; - case ENTITY_VIEW -> entityViewService.findEntityViewById(tenantId, new EntityViewId(entityId.getId())) != null; - case CUSTOMER -> customerService.findCustomerById(tenantId, new CustomerId(entityId.getId())) != null; - case USER -> userService.findUserById(tenantId, new UserId(entityId.getId())) != null; - case DASHBOARD -> dashboardService.findDashboardById(tenantId, new DashboardId(entityId.getId())) != null; - case EDGE -> edgeService.findEdgeById(tenantId, new EdgeId(entityId.getId())) != null; - default -> false; - }; + return entityDaoRegistry.getDao(entityId.getEntityType()).existsById(tenantId, entityId.getId()); } protected void createRelationFromEdge(TenantId tenantId, EdgeId edgeId, EntityId entityId) { @@ -595,7 +328,7 @@ public abstract class BaseEdgeProcessor { relation.setTo(entityId); relation.setTypeGroup(RelationTypeGroup.COMMON); relation.setType(EntityRelation.EDGE_TYPE); - relationService.saveRelation(tenantId, relation); + edgeCtx.getRelationService().saveRelation(tenantId, relation); } protected TbMsgMetaData getEdgeActionTbMsgMetaData(Edge edge, CustomerId customerId) { @@ -611,7 +344,7 @@ public abstract class BaseEdgeProcessor { protected void pushEntityEventToRuleEngine(TenantId tenantId, EntityId entityId, CustomerId customerId, TbMsgType msgType, String msgData, TbMsgMetaData metaData) { TbMsg tbMsg = TbMsg.newMsg(msgType, entityId, customerId, metaData, TbMsgDataType.JSON, msgData); - tbClusterService.pushMsgToRuleEngine(tenantId, entityId, tbMsg, new TbQueueCallback() { + edgeCtx.getClusterService().pushMsgToRuleEngine(tenantId, entityId, tbMsg, new TbQueueCallback() { @Override public void onSuccess(TbQueueMsgMetadata metadata) { log.debug("[{}] Successfully send ENTITY_CREATED EVENT to rule engine [{}]", tenantId, msgData); @@ -624,39 +357,4 @@ public abstract class BaseEdgeProcessor { }); } - protected AssetProfile checkIfAssetProfileDefaultFieldsAssignedToEdge(TenantId tenantId, EdgeId edgeId, AssetProfile assetProfile, EdgeVersion edgeVersion) { - if (EdgeVersion.V_3_3_0.equals(edgeVersion) || EdgeVersion.V_3_3_3.equals(edgeVersion) || EdgeVersion.V_3_4_0.equals(edgeVersion)) { - if (assetProfile.getDefaultDashboardId() != null && isEntityNotAssignedToEdge(tenantId, assetProfile.getDefaultDashboardId(), edgeId)) { - assetProfile.setDefaultDashboardId(null); - } - if (assetProfile.getDefaultEdgeRuleChainId() != null && isEntityNotAssignedToEdge(tenantId, assetProfile.getDefaultEdgeRuleChainId(), edgeId)) { - assetProfile.setDefaultEdgeRuleChainId(null); - } - } - return assetProfile; - } - - protected DeviceProfile checkIfDeviceProfileDefaultFieldsAssignedToEdge(TenantId tenantId, EdgeId edgeId, DeviceProfile deviceProfile, EdgeVersion edgeVersion) { - if (EdgeVersion.V_3_3_0.equals(edgeVersion) || EdgeVersion.V_3_3_3.equals(edgeVersion) || EdgeVersion.V_3_4_0.equals(edgeVersion)) { - if (deviceProfile.getDefaultDashboardId() != null && isEntityNotAssignedToEdge(tenantId, deviceProfile.getDefaultDashboardId(), edgeId)) { - deviceProfile.setDefaultDashboardId(null); - } - if (deviceProfile.getDefaultEdgeRuleChainId() != null && isEntityNotAssignedToEdge(tenantId, deviceProfile.getDefaultEdgeRuleChainId(), edgeId)) { - deviceProfile.setDefaultEdgeRuleChainId(null); - } - } - return deviceProfile; - } - - private boolean isEntityNotAssignedToEdge(TenantId tenantId, EntityId entityId, EdgeId edgeId) { - PageDataIterableByTenantIdEntityId edgeIds = - new PageDataIterableByTenantIdEntityId<>(edgeService::findRelatedEdgeIdsByEntityId, tenantId, entityId, RELATED_EDGES_CACHE_ITEMS); - for (EdgeId edgeId1 : edgeIds) { - if (edgeId1.equals(edgeId)) { - return false; - } - } - return true; - } - } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessor.java index 009c5854cd..f0a8012660 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessor.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.alarm.Alarm; @@ -31,6 +32,7 @@ import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageDataIterableByTenantIdEntityId; +import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.gen.edge.v1.AlarmCommentUpdateMsg; import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; @@ -38,6 +40,7 @@ import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.service.edge.rpc.constructor.alarm.AlarmMsgConstructor; +import org.thingsboard.server.service.edge.rpc.constructor.alarm.AlarmMsgConstructorFactory; import java.util.ArrayList; import java.util.List; @@ -48,6 +51,12 @@ import static org.thingsboard.server.dao.edge.BaseRelatedEdgesService.RELATED_ED @Slf4j public abstract class AlarmEdgeProcessor extends BaseAlarmProcessor implements AlarmProcessor { + @Autowired + private AlarmMsgConstructorFactory alarmMsgConstructorFactory; + + @Autowired + private EntityService entityService; + @Override public ListenableFuture processAlarmMsgFromEdge(TenantId tenantId, EdgeId edgeId, AlarmUpdateMsg alarmUpdateMsg) { log.trace("[{}] processAlarmMsgFromEdge [{}]", tenantId, alarmUpdateMsg); @@ -86,6 +95,7 @@ public abstract class AlarmEdgeProcessor extends BaseAlarmProcessor implements A @Override public DownlinkMsg convertAlarmCommentEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); + var msgConstructor = (AlarmMsgConstructor) alarmMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion); switch (edgeEvent.getAction()) { case ADDED_COMMENT: case UPDATED_COMMENT: @@ -94,8 +104,7 @@ public abstract class AlarmEdgeProcessor extends BaseAlarmProcessor implements A if (alarmComment != null) { return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) - .addAlarmCommentUpdateMsg(((AlarmMsgConstructor) alarmMsgConstructorFactory - .getMsgConstructorByEdgeVersion(edgeVersion)).constructAlarmCommentUpdatedMsg(msgType, alarmComment)) + .addAlarmCommentUpdateMsg(msgConstructor.constructAlarmCommentUpdatedMsg(msgType, alarmComment)) .build(); } default: @@ -116,7 +125,7 @@ public abstract class AlarmEdgeProcessor extends BaseAlarmProcessor implements A alarmId, actionType, JacksonUtil.valueToTree(deletedAlarm), originatorEdgeId, EdgeEventType.ALARM); return Futures.transform(Futures.allAsList(delFutures), voids -> null, dbCallbackExecutorService); } - ListenableFuture alarmFuture = alarmService.findAlarmByIdAsync(tenantId, alarmId); + ListenableFuture alarmFuture = edgeCtx.getAlarmService().findAlarmByIdAsync(tenantId, alarmId); return Futures.transformAsync(alarmFuture, alarm -> { if (alarm == null) { return Futures.immediateFuture(null); @@ -139,7 +148,7 @@ public abstract class AlarmEdgeProcessor extends BaseAlarmProcessor implements A if (alarmComment == null) { return Futures.immediateFuture(null); } - Alarm alarmById = alarmService.findAlarmById(tenantId, new AlarmId(alarmComment.getAlarmId().getId())); + Alarm alarmById = edgeCtx.getAlarmService().findAlarmById(tenantId, new AlarmId(alarmComment.getAlarmId().getId())); List> delFutures = pushEventToAllRelatedEdges(tenantId, alarmById.getOriginator(), alarmId, actionType, JacksonUtil.valueToTree(alarmComment), originatorEdgeId, EdgeEventType.ALARM_COMMENT); return Futures.transform(Futures.allAsList(delFutures), voids -> null, dbCallbackExecutorService); @@ -150,7 +159,7 @@ public abstract class AlarmEdgeProcessor extends BaseAlarmProcessor implements A EdgeEventType edgeEventType) { List> futures = new ArrayList<>(); PageDataIterableByTenantIdEntityId edgeIds = - new PageDataIterableByTenantIdEntityId<>(edgeService::findRelatedEdgeIdsByEntityId, tenantId, originatorId, RELATED_EDGES_CACHE_ITEMS); + new PageDataIterableByTenantIdEntityId<>(edgeCtx.getEdgeService()::findRelatedEdgeIdsByEntityId, tenantId, originatorId, RELATED_EDGES_CACHE_ITEMS); for (EdgeId relatedEdgeId : edgeIds) { if (!relatedEdgeId.equals(sourceEdgeId)) { futures.add(saveEdgeEvent(tenantId, relatedEdgeId, edgeEventType, actionType, alarmId, body)); @@ -159,4 +168,27 @@ public abstract class AlarmEdgeProcessor extends BaseAlarmProcessor implements A return futures; } + private AlarmUpdateMsg convertAlarmEventToAlarmMsg(TenantId tenantId, UUID entityId, EdgeEventActionType actionType, JsonNode body, EdgeVersion edgeVersion) { + AlarmId alarmId = new AlarmId(entityId); + UpdateMsgType msgType = getUpdateMsgType(actionType); + var msgConstructor = (AlarmMsgConstructor) alarmMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion); + switch (actionType) { + case ADDED, UPDATED, ALARM_ACK, ALARM_CLEAR -> { + Alarm alarm = edgeCtx.getAlarmService().findAlarmById(tenantId, alarmId); + if (alarm != null) { + return msgConstructor.constructAlarmUpdatedMsg(msgType, alarm, + entityService.fetchEntityName(tenantId, alarm.getOriginator()).orElse(null)); + } + } + case ALARM_DELETE, DELETED -> { + Alarm deletedAlarm = JacksonUtil.convertValue(body, Alarm.class); + if (deletedAlarm != null) { + return msgConstructor.constructAlarmUpdatedMsg(msgType, deletedAlarm, + entityService.fetchEntityName(tenantId, deletedAlarm.getOriginator()).orElse(null)); + } + } + } + return null; + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessorV1.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessorV1.java index 30ee4efe5f..18e65e6bac 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessorV1.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessorV1.java @@ -59,9 +59,9 @@ public class AlarmEdgeProcessorV1 extends AlarmEdgeProcessor { private EntityId getAlarmOriginator(TenantId tenantId, String entityName, EntityType entityType) { return switch (entityType) { - case DEVICE -> deviceService.findDeviceByTenantIdAndName(tenantId, entityName).getId(); - case ASSET -> assetService.findAssetByTenantIdAndName(tenantId, entityName).getId(); - case ENTITY_VIEW -> entityViewService.findEntityViewByTenantIdAndName(tenantId, entityName).getId(); + case DEVICE -> edgeCtx.getDeviceService().findDeviceByTenantIdAndName(tenantId, entityName).getId(); + case ASSET -> edgeCtx.getAssetService().findAssetByTenantIdAndName(tenantId, entityName).getId(); + case ENTITY_VIEW -> edgeCtx.getEntityViewService().findEntityViewByTenantIdAndName(tenantId, entityName).getId(); default -> null; }; } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java index f6a05e363c..195b569ab4 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java @@ -15,32 +15,21 @@ */ package org.thingsboard.server.service.edge.rpc.processor.alarm; -import com.fasterxml.jackson.databind.JsonNode; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmComment; import org.thingsboard.server.common.data.alarm.AlarmCreateOrUpdateActiveRequest; import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest; -import org.thingsboard.server.common.data.asset.Asset; -import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.id.AlarmId; -import org.thingsboard.server.common.data.id.AssetId; -import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.alarm.AlarmCommentDao; import org.thingsboard.server.gen.edge.v1.AlarmCommentUpdateMsg; import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg; -import org.thingsboard.server.gen.edge.v1.EdgeVersion; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.service.edge.rpc.constructor.alarm.AlarmMsgConstructor; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; import java.util.UUID; @@ -66,27 +55,27 @@ public abstract class BaseAlarmProcessor extends BaseEdgeProcessor { try { switch (alarmUpdateMsg.getMsgType()) { case ENTITY_CREATED_RPC_MESSAGE: - alarmService.createAlarm(AlarmCreateOrUpdateActiveRequest.fromAlarm(alarm, null, alarmId)); + edgeCtx.getAlarmService().createAlarm(AlarmCreateOrUpdateActiveRequest.fromAlarm(alarm, null, alarmId)); break; case ENTITY_UPDATED_RPC_MESSAGE: - alarmService.updateAlarm(AlarmUpdateRequest.fromAlarm(alarm)); + edgeCtx.getAlarmService().updateAlarm(AlarmUpdateRequest.fromAlarm(alarm)); break; case ALARM_ACK_RPC_MESSAGE: - Alarm alarmToAck = alarmService.findAlarmById(tenantId, alarmId); + Alarm alarmToAck = edgeCtx.getAlarmService().findAlarmById(tenantId, alarmId); if (alarmToAck != null) { - alarmService.acknowledgeAlarm(tenantId, alarmId, alarm.getAckTs()); + edgeCtx.getAlarmService().acknowledgeAlarm(tenantId, alarmId, alarm.getAckTs()); } break; case ALARM_CLEAR_RPC_MESSAGE: - Alarm alarmToClear = alarmService.findAlarmById(tenantId, alarmId); + Alarm alarmToClear = edgeCtx.getAlarmService().findAlarmById(tenantId, alarmId); if (alarmToClear != null) { - alarmService.clearAlarm(tenantId, alarmId, alarm.getClearTs(), alarm.getDetails()); + edgeCtx.getAlarmService().clearAlarm(tenantId, alarmId, alarm.getClearTs(), alarm.getDetails()); } break; case ENTITY_DELETED_RPC_MESSAGE: - Alarm alarmToDelete = alarmService.findAlarmById(tenantId, alarmId); + Alarm alarmToDelete = edgeCtx.getAlarmService().findAlarmById(tenantId, alarmId); if (alarmToDelete != null) { - alarmService.delAlarm(tenantId, alarmId); + edgeCtx.getAlarmService().delAlarm(tenantId, alarmId); } break; case UNRECOGNIZED: @@ -107,7 +96,7 @@ public abstract class BaseAlarmProcessor extends BaseEdgeProcessor { throw new RuntimeException("[{" + tenantId + "}] alarmCommentUpdateMsg {" + alarmCommentUpdateMsg + "} cannot be converted to alarm comment"); } try { - Alarm alarm = alarmService.findAlarmById(tenantId, new AlarmId(alarmComment.getAlarmId().getId())); + Alarm alarm = edgeCtx.getAlarmService().findAlarmById(tenantId, new AlarmId(alarmComment.getAlarmId().getId())); if (alarm == null) { return Futures.immediateFuture(null); } @@ -116,12 +105,12 @@ public abstract class BaseAlarmProcessor extends BaseEdgeProcessor { alarmCommentDao.save(tenantId, alarmComment); break; case ENTITY_UPDATED_RPC_MESSAGE: - alarmCommentService.createOrUpdateAlarmComment(tenantId, alarmComment); + edgeCtx.getAlarmCommentService().createOrUpdateAlarmComment(tenantId, alarmComment); break; case ENTITY_DELETED_RPC_MESSAGE: - AlarmComment alarmCommentToDelete = alarmCommentService.findAlarmCommentById(tenantId, alarmComment.getId()); + AlarmComment alarmCommentToDelete = edgeCtx.getAlarmCommentService().findAlarmCommentById(tenantId, alarmComment.getId()); if (alarmCommentToDelete != null) { - alarmCommentService.saveAlarmComment(tenantId, alarmCommentToDelete); + edgeCtx.getAlarmCommentService().saveAlarmComment(tenantId, alarmCommentToDelete); } break; case UNRECOGNIZED: @@ -140,51 +129,4 @@ public abstract class BaseAlarmProcessor extends BaseEdgeProcessor { protected abstract Alarm constructAlarmFromUpdateMsg(TenantId tenantId, AlarmId alarmId, EntityId originatorId, AlarmUpdateMsg alarmUpdateMsg); - protected AlarmUpdateMsg convertAlarmEventToAlarmMsg(TenantId tenantId, UUID entityId, EdgeEventActionType actionType, JsonNode body, EdgeVersion edgeVersion) { - AlarmId alarmId = new AlarmId(entityId); - UpdateMsgType msgType = getUpdateMsgType(actionType); - switch (actionType) { - case ADDED, UPDATED, ALARM_ACK, ALARM_CLEAR -> { - Alarm alarm = alarmService.findAlarmById(tenantId, alarmId); - if (alarm != null) { - return ((AlarmMsgConstructor) alarmMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)) - .constructAlarmUpdatedMsg(msgType, alarm, findOriginatorEntityName(tenantId, alarm)); - } - } - case ALARM_DELETE, DELETED -> { - Alarm deletedAlarm = JacksonUtil.convertValue(body, Alarm.class); - if (deletedAlarm != null) { - return ((AlarmMsgConstructor) alarmMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)) - .constructAlarmUpdatedMsg(msgType, deletedAlarm, findOriginatorEntityName(tenantId, deletedAlarm)); - } - } - } - return null; - } - - private String findOriginatorEntityName(TenantId tenantId, Alarm alarm) { - String entityName = null; - switch (alarm.getOriginator().getEntityType()) { - case DEVICE -> { - Device deviceById = deviceService.findDeviceById(tenantId, new DeviceId(alarm.getOriginator().getId())); - if (deviceById != null) { - entityName = deviceById.getName(); - } - } - case ASSET -> { - Asset assetById = assetService.findAssetById(tenantId, new AssetId(alarm.getOriginator().getId())); - if (assetById != null) { - entityName = assetById.getName(); - } - } - case ENTITY_VIEW -> { - EntityView entityViewById = entityViewService.findEntityViewById(tenantId, new EntityViewId(alarm.getOriginator().getId())); - if (entityViewById != null) { - entityName = entityViewById.getName(); - } - } - } - return entityName; - } - } 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 3e3c9dfb64..56a4864763 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 @@ -28,7 +28,6 @@ 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.AssetId; -import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -57,9 +56,9 @@ public abstract class AssetEdgeProcessor extends BaseAssetProcessor implements A saveOrUpdateAsset(tenantId, assetId, assetUpdateMsg, edge); return Futures.immediateFuture(null); case ENTITY_DELETED_RPC_MESSAGE: - Asset assetToDelete = assetService.findAssetById(tenantId, assetId); + Asset assetToDelete = edgeCtx.getAssetService().findAssetById(tenantId, assetId); if (assetToDelete != null) { - assetService.unassignAssetFromEdge(tenantId, assetId, edge.getId()); + edgeCtx.getAssetService().unassignAssetFromEdge(tenantId, assetId, edge.getId()); } return Futures.immediateFuture(null); case UNRECOGNIZED: @@ -84,7 +83,7 @@ public abstract class AssetEdgeProcessor extends BaseAssetProcessor implements A if (created) { createRelationFromEdge(tenantId, edge.getId(), assetId); pushAssetCreatedEventToRuleEngine(tenantId, edge, assetId); - assetService.assignAssetToEdge(tenantId, assetId, edge.getId()); + edgeCtx.getAssetService().assignAssetToEdge(tenantId, assetId, edge.getId()); } Boolean assetNameUpdated = resultPair.getSecond(); if (assetNameUpdated) { @@ -94,7 +93,7 @@ public abstract class AssetEdgeProcessor extends BaseAssetProcessor implements A private void pushAssetCreatedEventToRuleEngine(TenantId tenantId, Edge edge, AssetId assetId) { try { - Asset asset = assetService.findAssetById(tenantId, assetId); + Asset asset = edgeCtx.getAssetService().findAssetById(tenantId, assetId); String assetAsString = JacksonUtil.toString(asset); TbMsgMetaData msgMetaData = getEdgeActionTbMsgMetaData(edge, asset.getCustomerId()); pushEntityEventToRuleEngine(tenantId, assetId, asset.getCustomerId(), TbMsgType.ENTITY_CREATED, assetAsString, msgMetaData); @@ -104,34 +103,33 @@ public abstract class AssetEdgeProcessor extends BaseAssetProcessor implements A } @Override - public DownlinkMsg convertAssetEventToDownlink(EdgeEvent edgeEvent, EdgeId edgeId, EdgeVersion edgeVersion) { + public DownlinkMsg convertAssetEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { AssetId assetId = new AssetId(edgeEvent.getEntityId()); - DownlinkMsg downlinkMsg = null; - var msgConstructor = (AssetMsgConstructor) assetMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion); + var msgConstructor = (AssetMsgConstructor) edgeCtx.getAssetMsgConstructorFactory().getMsgConstructorByEdgeVersion(edgeVersion); switch (edgeEvent.getAction()) { case ADDED, UPDATED, ASSIGNED_TO_EDGE, ASSIGNED_TO_CUSTOMER, UNASSIGNED_FROM_CUSTOMER -> { - Asset asset = assetService.findAssetById(edgeEvent.getTenantId(), assetId); + Asset asset = edgeCtx.getAssetService().findAssetById(edgeEvent.getTenantId(), assetId); if (asset != null) { UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); - AssetUpdateMsg assetUpdateMsg = ((AssetMsgConstructor) - assetMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)).constructAssetUpdatedMsg(msgType, asset); + AssetUpdateMsg assetUpdateMsg = msgConstructor.constructAssetUpdatedMsg(msgType, asset); DownlinkMsg.Builder builder = DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addAssetUpdateMsg(assetUpdateMsg); if (UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE.equals(msgType)) { - AssetProfile assetProfile = assetProfileService.findAssetProfileById(edgeEvent.getTenantId(), asset.getAssetProfileId()); - assetProfile = checkIfAssetProfileDefaultFieldsAssignedToEdge(edgeEvent.getTenantId(), edgeId, assetProfile, edgeVersion); + AssetProfile assetProfile = edgeCtx.getAssetProfileService().findAssetProfileById(edgeEvent.getTenantId(), asset.getAssetProfileId()); builder.addAssetProfileUpdateMsg(msgConstructor.constructAssetProfileUpdatedMsg(msgType, assetProfile)); } - downlinkMsg = builder.build(); + return builder.build(); } } - case DELETED, UNASSIGNED_FROM_EDGE -> downlinkMsg = DownlinkMsg.newBuilder() - .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) - .addAssetUpdateMsg(msgConstructor.constructAssetDeleteMsg(assetId)) - .build(); + case DELETED, UNASSIGNED_FROM_EDGE -> { + return DownlinkMsg.newBuilder() + .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) + .addAssetUpdateMsg(msgConstructor.constructAssetDeleteMsg(assetId)) + .build(); + } } - return downlinkMsg; + return null; } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetProcessor.java index 2685274bbf..f6b015701c 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetProcessor.java @@ -18,7 +18,6 @@ package org.thingsboard.server.service.edge.rpc.processor.asset; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; -import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; @@ -29,6 +28,6 @@ public interface AssetProcessor extends EdgeProcessor { ListenableFuture processAssetMsgFromEdge(TenantId tenantId, Edge edge, AssetUpdateMsg assetUpdateMsg); - DownlinkMsg convertAssetEventToDownlink(EdgeEvent edgeEvent, EdgeId edgeId, EdgeVersion edgeVersion); + DownlinkMsg convertAssetEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/BaseAssetProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/BaseAssetProcessor.java index 2588e52d5f..9ebcd74888 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/BaseAssetProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/BaseAssetProcessor.java @@ -16,18 +16,23 @@ package org.thingsboard.server.service.edge.rpc.processor.asset; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.util.Pair; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @Slf4j public abstract class BaseAssetProcessor extends BaseEdgeProcessor { + @Autowired + private DataValidator assetValidator; + protected Pair saveOrUpdateAsset(TenantId tenantId, AssetId assetId, AssetUpdateMsg assetUpdateMsg) { boolean created = false; boolean assetNameUpdated = false; @@ -37,7 +42,7 @@ public abstract class BaseAssetProcessor extends BaseEdgeProcessor { if (asset == null) { throw new RuntimeException("[{" + tenantId + "}] assetUpdateMsg {" + assetUpdateMsg + " } cannot be converted to asset"); } - Asset assetById = assetService.findAssetById(tenantId, assetId); + Asset assetById = edgeCtx.getAssetService().findAssetById(tenantId, assetId); if (assetById == null) { created = true; asset.setId(null); @@ -45,7 +50,7 @@ public abstract class BaseAssetProcessor extends BaseEdgeProcessor { asset.setId(assetId); } String assetName = asset.getName(); - Asset assetByName = assetService.findAssetByTenantIdAndName(tenantId, assetName); + Asset assetByName = edgeCtx.getAssetService().findAssetByTenantIdAndName(tenantId, assetName); if (assetByName != null && !assetByName.getId().equals(assetId)) { assetName = assetName + "_" + StringUtils.randomAlphanumeric(15); log.warn("[{}] Asset with name {} already exists. Renaming asset name to {}", @@ -59,7 +64,7 @@ public abstract class BaseAssetProcessor extends BaseEdgeProcessor { if (created) { asset.setId(assetId); } - assetService.saveAsset(asset, false); + edgeCtx.getAssetService().saveAsset(asset, false); } catch (Exception e) { log.error("[{}] Failed to process asset update msg [{}]", tenantId, assetUpdateMsg, e); throw e; 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 b4d56ed20f..1998def9fd 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 @@ -50,16 +50,13 @@ public abstract class AssetProfileEdgeProcessor extends BaseAssetProfileProcesso try { edgeSynchronizationManager.getEdgeId().set(edge.getId()); - switch (assetProfileUpdateMsg.getMsgType()) { - case ENTITY_CREATED_RPC_MESSAGE: - case ENTITY_UPDATED_RPC_MESSAGE: + return switch (assetProfileUpdateMsg.getMsgType()) { + case ENTITY_CREATED_RPC_MESSAGE, ENTITY_UPDATED_RPC_MESSAGE -> { saveOrUpdateAssetProfile(tenantId, assetProfileId, assetProfileUpdateMsg, edge); - return Futures.immediateFuture(null); - case ENTITY_DELETED_RPC_MESSAGE: - case UNRECOGNIZED: - default: - return handleUnsupportedMsgType(assetProfileUpdateMsg.getMsgType()); - } + yield Futures.immediateFuture(null); + } + default -> handleUnsupportedMsgType(assetProfileUpdateMsg.getMsgType()); + }; } catch (DataValidationException e) { log.warn("[{}] Failed to process AssetProfileUpdateMsg from Edge [{}]", tenantId, assetProfileUpdateMsg, e); return Futures.immediateFailedFuture(e); @@ -83,7 +80,7 @@ public abstract class AssetProfileEdgeProcessor extends BaseAssetProfileProcesso private void pushAssetProfileCreatedEventToRuleEngine(TenantId tenantId, Edge edge, AssetProfileId assetProfileId) { try { - AssetProfile assetProfile = assetProfileService.findAssetProfileById(tenantId, assetProfileId); + AssetProfile assetProfile = edgeCtx.getAssetProfileService().findAssetProfileById(tenantId, assetProfileId); String assetProfileAsString = JacksonUtil.toString(assetProfile); TbMsgMetaData msgMetaData = getEdgeActionTbMsgMetaData(edge, null); pushEntityEventToRuleEngine(tenantId, assetProfileId, null, TbMsgType.ENTITY_CREATED, assetProfileAsString, msgMetaData); @@ -93,33 +90,30 @@ public abstract class AssetProfileEdgeProcessor extends BaseAssetProfileProcesso } @Override - public DownlinkMsg convertAssetProfileEventToDownlink(EdgeEvent edgeEvent, EdgeId edgeId, EdgeVersion edgeVersion) { + public DownlinkMsg convertAssetProfileEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { AssetProfileId assetProfileId = new AssetProfileId(edgeEvent.getEntityId()); - DownlinkMsg downlinkMsg = null; + var msgConstructor = (AssetMsgConstructor) edgeCtx.getAssetMsgConstructorFactory().getMsgConstructorByEdgeVersion(edgeVersion); switch (edgeEvent.getAction()) { case ADDED, UPDATED -> { - AssetProfile assetProfile = assetProfileService.findAssetProfileById(edgeEvent.getTenantId(), assetProfileId); + AssetProfile assetProfile = edgeCtx.getAssetProfileService().findAssetProfileById(edgeEvent.getTenantId(), assetProfileId); if (assetProfile != null) { UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); - assetProfile = checkIfAssetProfileDefaultFieldsAssignedToEdge(edgeEvent.getTenantId(), edgeId, assetProfile, edgeVersion); - AssetProfileUpdateMsg assetProfileUpdateMsg = ((AssetMsgConstructor) - assetMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)).constructAssetProfileUpdatedMsg(msgType, assetProfile); - downlinkMsg = DownlinkMsg.newBuilder() + AssetProfileUpdateMsg assetProfileUpdateMsg = msgConstructor.constructAssetProfileUpdatedMsg(msgType, assetProfile); + return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addAssetProfileUpdateMsg(assetProfileUpdateMsg) .build(); } } case DELETED -> { - AssetProfileUpdateMsg assetProfileUpdateMsg = ((AssetMsgConstructor) - assetMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)).constructAssetProfileDeleteMsg(assetProfileId); - downlinkMsg = DownlinkMsg.newBuilder() + AssetProfileUpdateMsg assetProfileUpdateMsg = msgConstructor.constructAssetProfileDeleteMsg(assetProfileId); + return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addAssetProfileUpdateMsg(assetProfileUpdateMsg) .build(); } } - return downlinkMsg; + return null; } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/AssetProfileProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/AssetProfileProcessor.java index ba7aa106cf..83051c519c 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/AssetProfileProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/AssetProfileProcessor.java @@ -18,7 +18,6 @@ package org.thingsboard.server.service.edge.rpc.processor.asset.profile; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; -import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; @@ -29,5 +28,6 @@ public interface AssetProfileProcessor extends EdgeProcessor { ListenableFuture processAssetProfileMsgFromEdge(TenantId tenantId, Edge edge, AssetProfileUpdateMsg assetProfileUpdateMsg); - DownlinkMsg convertAssetProfileEventToDownlink(EdgeEvent edgeEvent, EdgeId edgeId, EdgeVersion edgeVersion); + DownlinkMsg convertAssetProfileEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion); + } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/BaseAssetProfileProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/BaseAssetProfileProcessor.java index 3a4dbc86ed..2b1440a480 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/BaseAssetProfileProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/BaseAssetProfileProcessor.java @@ -16,6 +16,7 @@ package org.thingsboard.server.service.edge.rpc.processor.asset.profile; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.util.Pair; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.asset.AssetProfile; @@ -23,12 +24,16 @@ import org.thingsboard.server.common.data.id.AssetProfileId; import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @Slf4j public abstract class BaseAssetProfileProcessor extends BaseEdgeProcessor { + @Autowired + private DataValidator assetProfileValidator; + protected Pair saveOrUpdateAssetProfile(TenantId tenantId, AssetProfileId assetProfileId, AssetProfileUpdateMsg assetProfileUpdateMsg) { boolean created = false; boolean assetProfileNameUpdated = false; @@ -38,7 +43,7 @@ public abstract class BaseAssetProfileProcessor extends BaseEdgeProcessor { if (assetProfile == null) { throw new RuntimeException("[{" + tenantId + "}] assetProfileUpdateMsg {" + assetProfileUpdateMsg + "} cannot be converted to asset profile"); } - AssetProfile assetProfileById = assetProfileService.findAssetProfileById(tenantId, assetProfileId); + AssetProfile assetProfileById = edgeCtx.getAssetProfileService().findAssetProfileById(tenantId, assetProfileId); if (assetProfileById == null) { created = true; assetProfile.setId(null); @@ -48,7 +53,7 @@ public abstract class BaseAssetProfileProcessor extends BaseEdgeProcessor { assetProfile.setDefault(assetProfileById.isDefault()); } String assetProfileName = assetProfile.getName(); - AssetProfile assetProfileByName = assetProfileService.findAssetProfileByName(tenantId, assetProfileName); + AssetProfile assetProfileByName = edgeCtx.getAssetProfileService().findAssetProfileByName(tenantId, assetProfileName); if (assetProfileByName != null && !assetProfileByName.getId().equals(assetProfileId)) { assetProfileName = assetProfileName + "_" + StringUtils.randomAlphabetic(15); log.warn("[{}] Asset profile with name {} already exists. Renaming asset profile name to {}", @@ -66,7 +71,7 @@ public abstract class BaseAssetProfileProcessor extends BaseEdgeProcessor { if (created) { assetProfile.setId(assetProfileId); } - assetProfileService.saveAssetProfile(assetProfile, false, true); + edgeCtx.getAssetProfileService().saveAssetProfile(assetProfile, false, true); } catch (Exception e) { log.error("[{}] Failed to process asset profile update msg [{}]", tenantId, assetProfileUpdateMsg, e); throw e; @@ -83,4 +88,5 @@ public abstract class BaseAssetProfileProcessor extends BaseEdgeProcessor { protected abstract void setDefaultEdgeRuleChainId(AssetProfile assetProfile, RuleChainId ruleChainId, AssetProfileUpdateMsg assetProfileUpdateMsg); protected abstract void setDefaultDashboardId(TenantId tenantId, DashboardId dashboardId, AssetProfile assetProfile, AssetProfileUpdateMsg assetProfileUpdateMsg); + } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/customer/CustomerEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/customer/CustomerEdgeProcessor.java index e13c4c487f..ddf6f25728 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/customer/CustomerEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/customer/CustomerEdgeProcessor.java @@ -18,6 +18,7 @@ package org.thingsboard.server.service.edge.rpc.processor.customer; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.EdgeUtils; @@ -37,6 +38,7 @@ import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.edge.rpc.constructor.customer.CustomerMsgConstructor; +import org.thingsboard.server.service.edge.rpc.constructor.customer.CustomerMsgConstructorFactory; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; import java.util.ArrayList; @@ -48,32 +50,33 @@ import java.util.UUID; @TbCoreComponent public class CustomerEdgeProcessor extends BaseEdgeProcessor { + @Autowired + private CustomerMsgConstructorFactory customerMsgConstructorFactory; + public DownlinkMsg convertCustomerEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { CustomerId customerId = new CustomerId(edgeEvent.getEntityId()); - DownlinkMsg downlinkMsg = null; + var msgConstructor = (CustomerMsgConstructor) customerMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion); switch (edgeEvent.getAction()) { case ADDED, UPDATED -> { - Customer customer = customerService.findCustomerById(edgeEvent.getTenantId(), customerId); + Customer customer = edgeCtx.getCustomerService().findCustomerById(edgeEvent.getTenantId(), customerId); if (customer != null) { UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); - CustomerUpdateMsg customerUpdateMsg = ((CustomerMsgConstructor) - customerMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)).constructCustomerUpdatedMsg(msgType, customer); - downlinkMsg = DownlinkMsg.newBuilder() + CustomerUpdateMsg customerUpdateMsg = msgConstructor.constructCustomerUpdatedMsg(msgType, customer); + return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addCustomerUpdateMsg(customerUpdateMsg) .build(); } } case DELETED -> { - CustomerUpdateMsg customerUpdateMsg = ((CustomerMsgConstructor) - customerMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)).constructCustomerDeleteMsg(customerId); - downlinkMsg = DownlinkMsg.newBuilder() + CustomerUpdateMsg customerUpdateMsg = msgConstructor.constructCustomerDeleteMsg(customerId); + return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addCustomerUpdateMsg(customerUpdateMsg) .build(); } } - return downlinkMsg; + return null; } public ListenableFuture processCustomerNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { @@ -84,7 +87,7 @@ public class CustomerEdgeProcessor extends BaseEdgeProcessor { switch (actionType) { case UPDATED: List> futures = new ArrayList<>(); - PageDataIterable edges = new PageDataIterable<>(link -> edgeService.findEdgesByTenantIdAndCustomerId(tenantId, customerId, link), 1024); + PageDataIterable edges = new PageDataIterable<>(link -> edgeCtx.getEdgeService().findEdgesByTenantIdAndCustomerId(tenantId, customerId, link), 1024); for (Edge edge : edges) { futures.add(saveEdgeEvent(tenantId, edge.getId(), type, actionType, customerId, null)); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/BaseDashboardProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/BaseDashboardProcessor.java index 64fb1e3981..52277b8527 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/BaseDashboardProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/BaseDashboardProcessor.java @@ -16,11 +16,13 @@ package org.thingsboard.server.service.edge.rpc.processor.dashboard; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.ShortCustomerInfo; 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.dao.service.DataValidator; import org.thingsboard.server.gen.edge.v1.DashboardUpdateMsg; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @@ -29,6 +31,9 @@ import java.util.Set; @Slf4j public abstract class BaseDashboardProcessor extends BaseEdgeProcessor { + @Autowired + private DataValidator dashboardValidator; + protected boolean saveOrUpdateDashboard(TenantId tenantId, DashboardId dashboardId, DashboardUpdateMsg dashboardUpdateMsg, CustomerId customerId) { boolean created = false; Dashboard dashboard = constructDashboardFromUpdateMsg(tenantId, dashboardId, dashboardUpdateMsg); @@ -36,7 +41,7 @@ public abstract class BaseDashboardProcessor extends BaseEdgeProcessor { throw new RuntimeException("[{" + tenantId + "}] dashboardUpdateMsg {" + dashboardUpdateMsg + "} cannot be converted to dashboard"); } Set assignedCustomers = null; - Dashboard dashboardById = dashboardService.findDashboardById(tenantId, dashboardId); + Dashboard dashboardById = edgeCtx.getDashboardService().findDashboardById(tenantId, dashboardId); if (dashboardById == null) { created = true; dashboard.setId(null); @@ -58,11 +63,11 @@ public abstract class BaseDashboardProcessor extends BaseEdgeProcessor { } } dashboard.setAssignedCustomers(assignedCustomers); - Dashboard savedDashboard = dashboardService.saveDashboard(dashboard, false); + Dashboard savedDashboard = edgeCtx.getDashboardService().saveDashboard(dashboard, false); if (msgAssignedCustomers != null && !msgAssignedCustomers.isEmpty()) { for (ShortCustomerInfo assignedCustomer : msgAssignedCustomers) { if (assignedCustomer.getCustomerId().equals(customerId)) { - dashboardService.assignDashboardToCustomer(tenantId, savedDashboard.getId(), assignedCustomer.getCustomerId()); + edgeCtx.getDashboardService().assignDashboardToCustomer(tenantId, savedDashboard.getId(), assignedCustomer.getCustomerId()); } } } else { @@ -75,7 +80,7 @@ public abstract class BaseDashboardProcessor extends BaseEdgeProcessor { if (dashboard.getAssignedCustomers() != null && !dashboard.getAssignedCustomers().isEmpty()) { for (ShortCustomerInfo assignedCustomer : dashboard.getAssignedCustomers()) { if (assignedCustomer.getCustomerId().equals(customerId)) { - dashboardService.unassignDashboardFromCustomer(tenantId, dashboard.getId(), assignedCustomer.getCustomerId()); + edgeCtx.getDashboardService().unassignDashboardFromCustomer(tenantId, dashboard.getId(), assignedCustomer.getCustomerId()); } } } @@ -84,4 +89,5 @@ public abstract class BaseDashboardProcessor extends BaseEdgeProcessor { protected abstract Dashboard constructDashboardFromUpdateMsg(TenantId tenantId, DashboardId dashboardId, DashboardUpdateMsg dashboardUpdateMsg); protected abstract Set filterNonExistingCustomers(TenantId tenantId, Set assignedCustomers); + } 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 f5f25b1c56..cb54977c3c 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 @@ -18,6 +18,7 @@ package org.thingsboard.server.service.edge.rpc.processor.dashboard; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.EdgeUtils; @@ -34,6 +35,7 @@ import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.service.edge.rpc.constructor.dashboard.DashboardMsgConstructor; +import org.thingsboard.server.service.edge.rpc.constructor.dashboard.DashboardMsgConstructorFactory; import java.util.Set; import java.util.UUID; @@ -41,6 +43,9 @@ import java.util.UUID; @Slf4j public abstract class DashboardEdgeProcessor extends BaseDashboardProcessor implements DashboardProcessor { + @Autowired + private DashboardMsgConstructorFactory dashboardMsgConstructorFactory; + @Override public ListenableFuture processDashboardMsgFromEdge(TenantId tenantId, Edge edge, DashboardUpdateMsg dashboardUpdateMsg) { log.trace("[{}] executing processDashboardMsgFromEdge [{}] from edge [{}]", tenantId, dashboardUpdateMsg, edge.getId()); @@ -54,9 +59,9 @@ public abstract class DashboardEdgeProcessor extends BaseDashboardProcessor impl saveOrUpdateDashboard(tenantId, dashboardId, dashboardUpdateMsg, edge); return Futures.immediateFuture(null); case ENTITY_DELETED_RPC_MESSAGE: - Dashboard dashboardToDelete = dashboardService.findDashboardById(tenantId, dashboardId); + Dashboard dashboardToDelete = edgeCtx.getDashboardService().findDashboardById(tenantId, dashboardId); if (dashboardToDelete != null) { - dashboardService.unassignDashboardFromEdge(tenantId, dashboardId, edge.getId()); + edgeCtx.getDashboardService().unassignDashboardFromEdge(tenantId, dashboardId, edge.getId()); } return Futures.immediateFuture(null); case UNRECOGNIZED: @@ -76,18 +81,17 @@ public abstract class DashboardEdgeProcessor extends BaseDashboardProcessor impl } private void saveOrUpdateDashboard(TenantId tenantId, DashboardId dashboardId, DashboardUpdateMsg dashboardUpdateMsg, Edge edge) { - boolean created = super.saveOrUpdateDashboard(tenantId, dashboardId, dashboardUpdateMsg, - edge.getCustomerId()); + boolean created = super.saveOrUpdateDashboard(tenantId, dashboardId, dashboardUpdateMsg, edge.getCustomerId()); if (created) { createRelationFromEdge(tenantId, edge.getId(), dashboardId); pushDashboardCreatedEventToRuleEngine(tenantId, edge, dashboardId); - dashboardService.assignDashboardToEdge(tenantId, dashboardId, edge.getId()); + edgeCtx.getDashboardService().assignDashboardToEdge(tenantId, dashboardId, edge.getId()); } } private void pushDashboardCreatedEventToRuleEngine(TenantId tenantId, Edge edge, DashboardId dashboardId) { try { - Dashboard dashboard = dashboardService.findDashboardById(tenantId, dashboardId); + Dashboard dashboard = edgeCtx.getDashboardService().findDashboardById(tenantId, dashboardId); String dashboardAsString = JacksonUtil.toString(dashboard); TbMsgMetaData msgMetaData = getEdgeActionTbMsgMetaData(edge, null); pushEntityEventToRuleEngine(tenantId, dashboardId, null, TbMsgType.ENTITY_CREATED, dashboardAsString, msgMetaData); @@ -99,26 +103,28 @@ public abstract class DashboardEdgeProcessor extends BaseDashboardProcessor impl @Override public DownlinkMsg convertDashboardEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { DashboardId dashboardId = new DashboardId(edgeEvent.getEntityId()); - DownlinkMsg downlinkMsg = null; var msgConstructor = (DashboardMsgConstructor) dashboardMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion); switch (edgeEvent.getAction()) { case ADDED, UPDATED, ASSIGNED_TO_EDGE, ASSIGNED_TO_CUSTOMER, UNASSIGNED_FROM_CUSTOMER -> { - Dashboard dashboard = dashboardService.findDashboardById(edgeEvent.getTenantId(), dashboardId); + Dashboard dashboard = edgeCtx.getDashboardService().findDashboardById(edgeEvent.getTenantId(), dashboardId); if (dashboard != null) { UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); DashboardUpdateMsg dashboardUpdateMsg = msgConstructor.constructDashboardUpdatedMsg(msgType, dashboard); - downlinkMsg = DownlinkMsg.newBuilder() + return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addDashboardUpdateMsg(dashboardUpdateMsg) .build(); } } - case DELETED, UNASSIGNED_FROM_EDGE -> downlinkMsg = DownlinkMsg.newBuilder() - .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) - .addDashboardUpdateMsg(msgConstructor.constructDashboardDeleteMsg(dashboardId)) - .build(); + case DELETED, UNASSIGNED_FROM_EDGE -> { + DashboardUpdateMsg dashboardUpdateMsg = msgConstructor.constructDashboardDeleteMsg(dashboardId); + return DownlinkMsg.newBuilder() + .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) + .addDashboardUpdateMsg(dashboardUpdateMsg) + .build(); + } } - return downlinkMsg; + return null; } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardEdgeProcessorV2.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardEdgeProcessorV2.java index 36369edde3..10bfe555a6 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardEdgeProcessorV2.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardEdgeProcessorV2.java @@ -33,4 +33,5 @@ public class DashboardEdgeProcessorV2 extends DashboardEdgeProcessor { protected Dashboard constructDashboardFromUpdateMsg(TenantId tenantId, DashboardId dashboardId, DashboardUpdateMsg dashboardUpdateMsg) { return JacksonUtil.fromString(dashboardUpdateMsg.getEntity(), Dashboard.class, true); } + } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/BaseDeviceProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/BaseDeviceProcessor.java index 016273bef9..4b2757e510 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/BaseDeviceProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/BaseDeviceProcessor.java @@ -16,6 +16,7 @@ package org.thingsboard.server.service.edge.rpc.processor.device; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.util.Pair; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.StringUtils; @@ -23,6 +24,7 @@ import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.gen.edge.v1.DeviceCredentialsUpdateMsg; import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @@ -30,6 +32,9 @@ import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @Slf4j public abstract class BaseDeviceProcessor extends BaseEdgeProcessor { + @Autowired + private DataValidator deviceValidator; + protected Pair saveOrUpdateDevice(TenantId tenantId, DeviceId deviceId, DeviceUpdateMsg deviceUpdateMsg) { boolean created = false; boolean deviceNameUpdated = false; @@ -39,7 +44,7 @@ public abstract class BaseDeviceProcessor extends BaseEdgeProcessor { if (device == null) { throw new RuntimeException("[{" + tenantId + "}] deviceUpdateMsg {" + deviceUpdateMsg + "} cannot be converted to device"); } - Device deviceById = deviceService.findDeviceById(tenantId, deviceId); + Device deviceById = edgeCtx.getDeviceService().findDeviceById(tenantId, deviceId); if (deviceById == null) { created = true; device.setId(null); @@ -47,7 +52,7 @@ public abstract class BaseDeviceProcessor extends BaseEdgeProcessor { device.setId(deviceId); } String deviceName = device.getName(); - Device deviceByName = deviceService.findDeviceByTenantIdAndName(tenantId, deviceName); + Device deviceByName = edgeCtx.getDeviceService().findDeviceByTenantIdAndName(tenantId, deviceName); if (deviceByName != null && !deviceByName.getId().equals(deviceId)) { deviceName = deviceName + "_" + StringUtils.randomAlphabetic(15); log.warn("[{}] Device with name {} already exists. Renaming device name to {}", @@ -61,8 +66,8 @@ public abstract class BaseDeviceProcessor extends BaseEdgeProcessor { if (created) { device.setId(deviceId); } - Device savedDevice = deviceService.saveDevice(device, false); - tbClusterService.onDeviceUpdated(savedDevice, created ? null : device); + Device savedDevice = edgeCtx.getDeviceService().saveDevice(device, false); + edgeCtx.getClusterService().onDeviceUpdated(savedDevice, created ? null : device); } catch (Exception e) { log.error("[{}] Failed to process device update msg [{}]", tenantId, deviceUpdateMsg, e); throw e; @@ -77,12 +82,12 @@ public abstract class BaseDeviceProcessor extends BaseEdgeProcessor { if (deviceCredentials == null) { throw new RuntimeException("[{" + tenantId + "}] deviceCredentialsUpdateMsg {" + deviceCredentialsUpdateMsg + "} cannot be converted to device credentials"); } - Device device = deviceService.findDeviceById(tenantId, deviceCredentials.getDeviceId()); + Device device = edgeCtx.getDeviceService().findDeviceById(tenantId, deviceCredentials.getDeviceId()); if (device != null) { log.debug("[{}] Updating device credentials for device [{}]. New device credentials Id [{}], value [{}]", tenantId, device.getName(), deviceCredentials.getCredentialsId(), deviceCredentials.getCredentialsValue()); try { - DeviceCredentials deviceCredentialsByDeviceId = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, device.getId()); + DeviceCredentials deviceCredentialsByDeviceId = edgeCtx.getDeviceCredentialsService().findDeviceCredentialsByDeviceId(tenantId, device.getId()); if (deviceCredentialsByDeviceId == null) { deviceCredentialsByDeviceId = new DeviceCredentials(); deviceCredentialsByDeviceId.setDeviceId(device.getId()); @@ -90,7 +95,7 @@ public abstract class BaseDeviceProcessor extends BaseEdgeProcessor { deviceCredentialsByDeviceId.setCredentialsType(deviceCredentials.getCredentialsType()); deviceCredentialsByDeviceId.setCredentialsId(deviceCredentials.getCredentialsId()); deviceCredentialsByDeviceId.setCredentialsValue(deviceCredentials.getCredentialsValue()); - deviceCredentialsService.updateDeviceCredentials(tenantId, deviceCredentialsByDeviceId); + edgeCtx.getDeviceCredentialsService().updateDeviceCredentials(tenantId, deviceCredentialsByDeviceId); } catch (Exception e) { log.error("[{}] Can't update device credentials for device [{}], deviceCredentialsUpdateMsg [{}]", 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 52f09971ab..e453cbc26f 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 @@ -71,9 +71,9 @@ public abstract class DeviceEdgeProcessor extends BaseDeviceProcessor implements saveOrUpdateDevice(tenantId, deviceId, deviceUpdateMsg, edge); return Futures.immediateFuture(null); case ENTITY_DELETED_RPC_MESSAGE: - Device deviceToDelete = deviceService.findDeviceById(tenantId, deviceId); + Device deviceToDelete = edgeCtx.getDeviceService().findDeviceById(tenantId, deviceId); if (deviceToDelete != null) { - deviceService.unassignDeviceFromEdge(tenantId, deviceId, edge.getId()); + edgeCtx.getDeviceService().unassignDeviceFromEdge(tenantId, deviceId, edge.getId()); } return Futures.immediateFuture(null); case UNRECOGNIZED: @@ -111,7 +111,7 @@ public abstract class DeviceEdgeProcessor extends BaseDeviceProcessor implements if (created) { createRelationFromEdge(tenantId, edge.getId(), deviceId); pushDeviceCreatedEventToRuleEngine(tenantId, edge, deviceId); - deviceService.assignDeviceToEdge(tenantId, deviceId, edge.getId()); + edgeCtx.getDeviceService().assignDeviceToEdge(tenantId, deviceId, edge.getId()); } Boolean deviceNameUpdated = resultPair.getSecond(); if (deviceNameUpdated) { @@ -121,7 +121,7 @@ public abstract class DeviceEdgeProcessor extends BaseDeviceProcessor implements private void pushDeviceCreatedEventToRuleEngine(TenantId tenantId, Edge edge, DeviceId deviceId) { try { - Device device = deviceService.findDeviceById(tenantId, deviceId); + Device device = edgeCtx.getDeviceService().findDeviceById(tenantId, deviceId); String deviceAsString = JacksonUtil.toString(device); TbMsgMetaData msgMetaData = getEdgeActionTbMsgMetaData(edge, device.getCustomerId()); pushEntityEventToRuleEngine(tenantId, deviceId, device.getCustomerId(), TbMsgType.ENTITY_CREATED, deviceAsString, msgMetaData); @@ -168,7 +168,7 @@ public abstract class DeviceEdgeProcessor extends BaseDeviceProcessor implements new FromDeviceRpcResponseActorMsg(deviceRpcCallMsg.getRequestId(), tenantId, deviceId, response); - tbClusterService.pushMsgToCore(msg, callback); + edgeCtx.getClusterService().pushMsgToCore(msg, callback); return futureToSet; } @@ -181,7 +181,7 @@ public abstract class DeviceEdgeProcessor extends BaseDeviceProcessor implements metaData.putValue("serviceId", deviceRpcCallMsg.getServiceId()); metaData.putValue("sessionId", deviceRpcCallMsg.getSessionId()); metaData.putValue(DataConstants.EDGE_ID, edge.getId().toString()); - Device device = deviceService.findDeviceById(tenantId, deviceId); + Device device = edgeCtx.getDeviceService().findDeviceById(tenantId, deviceId); if (device != null) { metaData.putValue("deviceName", device.getName()); metaData.putValue("deviceType", device.getType()); @@ -192,7 +192,7 @@ public abstract class DeviceEdgeProcessor extends BaseDeviceProcessor implements data.put("params", deviceRpcCallMsg.getRequestMsg().getParams()); TbMsg tbMsg = TbMsg.newMsg(TbMsgType.TO_SERVER_RPC_REQUEST, deviceId, null, metaData, TbMsgDataType.JSON, JacksonUtil.toString(data)); - tbClusterService.pushMsgToRuleEngine(tenantId, deviceId, tbMsg, new TbQueueCallback() { + edgeCtx.getClusterService().pushMsgToRuleEngine(tenantId, deviceId, tbMsg, new TbQueueCallback() { @Override public void onSuccess(TbQueueMsgMetadata metadata) { log.debug("[{}] Successfully send TO_SERVER_RPC_REQUEST to rule engine [{}], deviceRpcCallMsg {}", @@ -213,47 +213,44 @@ public abstract class DeviceEdgeProcessor extends BaseDeviceProcessor implements } @Override - public DownlinkMsg convertDeviceEventToDownlink(EdgeEvent edgeEvent, EdgeId edgeId, EdgeVersion edgeVersion) { + public DownlinkMsg convertDeviceEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { DeviceId deviceId = new DeviceId(edgeEvent.getEntityId()); - DownlinkMsg downlinkMsg = null; - var msgConstructor = (DeviceMsgConstructor) deviceMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion); + var msgConstructor = (DeviceMsgConstructor) edgeCtx.getDeviceMsgConstructorFactory().getMsgConstructorByEdgeVersion(edgeVersion); switch (edgeEvent.getAction()) { case ADDED: case UPDATED: case ASSIGNED_TO_EDGE: case ASSIGNED_TO_CUSTOMER: case UNASSIGNED_FROM_CUSTOMER: - Device device = deviceService.findDeviceById(edgeEvent.getTenantId(), deviceId); + Device device = edgeCtx.getDeviceService().findDeviceById(edgeEvent.getTenantId(), deviceId); if (device != null) { UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); DeviceUpdateMsg deviceUpdateMsg = msgConstructor.constructDeviceUpdatedMsg(msgType, device); DownlinkMsg.Builder builder = DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addDeviceUpdateMsg(deviceUpdateMsg); - DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(edgeEvent.getTenantId(), deviceId); + DeviceCredentials deviceCredentials = edgeCtx.getDeviceCredentialsService().findDeviceCredentialsByDeviceId(edgeEvent.getTenantId(), deviceId); if (deviceCredentials != null) { DeviceCredentialsUpdateMsg deviceCredentialsUpdateMsg = msgConstructor.constructDeviceCredentialsUpdatedMsg(deviceCredentials); builder.addDeviceCredentialsUpdateMsg(deviceCredentialsUpdateMsg).build(); } if (UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE.equals(msgType)) { - DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(edgeEvent.getTenantId(), device.getDeviceProfileId()); - deviceProfile = checkIfDeviceProfileDefaultFieldsAssignedToEdge(edgeEvent.getTenantId(), edgeId, deviceProfile, edgeVersion); + DeviceProfile deviceProfile = edgeCtx.getDeviceProfileService().findDeviceProfileById(edgeEvent.getTenantId(), device.getDeviceProfileId()); builder.addDeviceProfileUpdateMsg(msgConstructor.constructDeviceProfileUpdatedMsg(msgType, deviceProfile)); } - downlinkMsg = builder.build(); + return builder.build(); } break; case DELETED: case UNASSIGNED_FROM_EDGE: - downlinkMsg = DownlinkMsg.newBuilder() + return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addDeviceUpdateMsg(msgConstructor.constructDeviceDeleteMsg(deviceId)) .build(); - break; case CREDENTIALS_UPDATED: - DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(edgeEvent.getTenantId(), deviceId); + DeviceCredentials deviceCredentials = edgeCtx.getDeviceCredentialsService().findDeviceCredentialsByDeviceId(edgeEvent.getTenantId(), deviceId); if (deviceCredentials != null) { - downlinkMsg = DownlinkMsg.newBuilder() + return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addDeviceCredentialsUpdateMsg(msgConstructor.constructDeviceCredentialsUpdatedMsg(deviceCredentials)) .build(); @@ -265,7 +262,7 @@ public abstract class DeviceEdgeProcessor extends BaseDeviceProcessor implements .addDeviceRpcCallMsg(msgConstructor.constructDeviceRpcCallMsg(edgeEvent.getEntityId(), edgeEvent.getBody())) .build(); } - return downlinkMsg; + return null; } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceProcessor.java index b55e47b294..087b5f36a7 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceProcessor.java @@ -33,7 +33,7 @@ public interface DeviceProcessor extends EdgeProcessor { ListenableFuture processDeviceCredentialsMsgFromEdge(TenantId tenantId, EdgeId edgeId, DeviceCredentialsUpdateMsg deviceCredentialsUpdateMsg); - DownlinkMsg convertDeviceEventToDownlink(EdgeEvent edgeEvent, EdgeId edgeId, EdgeVersion edgeVersion); + DownlinkMsg convertDeviceEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion); ListenableFuture processDeviceRpcCallFromEdge(TenantId tenantId, Edge edge, DeviceRpcCallMsg deviceRpcCallMsg); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/BaseDeviceProfileProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/BaseDeviceProfileProcessor.java index 5f84e40dee..d06618772d 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/BaseDeviceProfileProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/BaseDeviceProfileProcessor.java @@ -16,6 +16,7 @@ package org.thingsboard.server.service.edge.rpc.processor.device.profile; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.util.Pair; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.StringUtils; @@ -23,12 +24,16 @@ import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @Slf4j public abstract class BaseDeviceProfileProcessor extends BaseEdgeProcessor { + @Autowired + private DataValidator deviceProfileValidator; + protected Pair saveOrUpdateDeviceProfile(TenantId tenantId, DeviceProfileId deviceProfileId, DeviceProfileUpdateMsg deviceProfileUpdateMsg) { boolean created = false; boolean deviceProfileNameUpdated = false; @@ -38,7 +43,7 @@ public abstract class BaseDeviceProfileProcessor extends BaseEdgeProcessor { if (deviceProfile == null) { throw new RuntimeException("[{" + tenantId + "}] deviceProfileUpdateMsg {" + deviceProfileUpdateMsg + "} cannot be converted to device profile"); } - DeviceProfile deviceProfileById = deviceProfileService.findDeviceProfileById(tenantId, deviceProfileId); + DeviceProfile deviceProfileById = edgeCtx.getDeviceProfileService().findDeviceProfileById(tenantId, deviceProfileId); if (deviceProfileById == null) { created = true; deviceProfile.setId(null); @@ -48,7 +53,7 @@ public abstract class BaseDeviceProfileProcessor extends BaseEdgeProcessor { deviceProfile.setDefault(deviceProfileById.isDefault()); } String deviceProfileName = deviceProfile.getName(); - DeviceProfile deviceProfileByName = deviceProfileService.findDeviceProfileByName(tenantId, deviceProfileName); + DeviceProfile deviceProfileByName = edgeCtx.getDeviceProfileService().findDeviceProfileByName(tenantId, deviceProfileName); if (deviceProfileByName != null && !deviceProfileByName.getId().equals(deviceProfileId)) { deviceProfileName = deviceProfileName + "_" + StringUtils.randomAlphabetic(15); log.warn("[{}] Device profile with name {} already exists. Renaming device profile name to {}", @@ -66,7 +71,7 @@ public abstract class BaseDeviceProfileProcessor extends BaseEdgeProcessor { if (created) { deviceProfile.setId(deviceProfileId); } - deviceProfileService.saveDeviceProfile(deviceProfile, false, true); + edgeCtx.getDeviceProfileService().saveDeviceProfile(deviceProfile, false, true); } catch (Exception e) { log.error("[{}] Failed to process device profile update msg [{}]", tenantId, deviceProfileUpdateMsg, e); throw e; @@ -83,4 +88,5 @@ public abstract class BaseDeviceProfileProcessor extends BaseEdgeProcessor { protected abstract void setDefaultEdgeRuleChainId(DeviceProfile deviceProfile, RuleChainId ruleChainId, DeviceProfileUpdateMsg deviceProfileUpdateMsg); protected abstract void setDefaultDashboardId(TenantId tenantId, DashboardId dashboardId, DeviceProfile deviceProfile, DeviceProfileUpdateMsg deviceProfileUpdateMsg); + } 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 f547cbc3a1..a0a699d340 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 @@ -27,7 +27,6 @@ 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.DeviceProfileId; -import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -83,7 +82,7 @@ public abstract class DeviceProfileEdgeProcessor extends BaseDeviceProfileProces private void pushDeviceProfileCreatedEventToRuleEngine(TenantId tenantId, Edge edge, DeviceProfileId deviceProfileId) { try { - DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(tenantId, deviceProfileId); + DeviceProfile deviceProfile = edgeCtx.getDeviceProfileService().findDeviceProfileById(tenantId, deviceProfileId); String deviceProfileAsString = JacksonUtil.toString(deviceProfile); TbMsgMetaData msgMetaData = getEdgeActionTbMsgMetaData(edge, null); pushEntityEventToRuleEngine(tenantId, deviceProfileId, null, TbMsgType.ENTITY_CREATED, deviceProfileAsString, msgMetaData); @@ -93,18 +92,16 @@ public abstract class DeviceProfileEdgeProcessor extends BaseDeviceProfileProces } @Override - public DownlinkMsg convertDeviceProfileEventToDownlink(EdgeEvent edgeEvent, EdgeId edgeId, EdgeVersion edgeVersion) { + public DownlinkMsg convertDeviceProfileEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { DeviceProfileId deviceProfileId = new DeviceProfileId(edgeEvent.getEntityId()); - DownlinkMsg downlinkMsg = null; - var msgConstructor = (DeviceMsgConstructor) deviceMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion); + var msgConstructor = (DeviceMsgConstructor) edgeCtx.getDeviceMsgConstructorFactory().getMsgConstructorByEdgeVersion(edgeVersion); switch (edgeEvent.getAction()) { case ADDED, UPDATED -> { - DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(edgeEvent.getTenantId(), deviceProfileId); + DeviceProfile deviceProfile = edgeCtx.getDeviceProfileService().findDeviceProfileById(edgeEvent.getTenantId(), deviceProfileId); if (deviceProfile != null) { UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); - deviceProfile = checkIfDeviceProfileDefaultFieldsAssignedToEdge(edgeEvent.getTenantId(), edgeId, deviceProfile, edgeVersion); DeviceProfileUpdateMsg deviceProfileUpdateMsg = msgConstructor.constructDeviceProfileUpdatedMsg(msgType, deviceProfile); - downlinkMsg = DownlinkMsg.newBuilder() + return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addDeviceProfileUpdateMsg(deviceProfileUpdateMsg) .build(); @@ -112,13 +109,13 @@ public abstract class DeviceProfileEdgeProcessor extends BaseDeviceProfileProces } case DELETED -> { DeviceProfileUpdateMsg deviceProfileUpdateMsg = msgConstructor.constructDeviceProfileDeleteMsg(deviceProfileId); - downlinkMsg = DownlinkMsg.newBuilder() + return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addDeviceProfileUpdateMsg(deviceProfileUpdateMsg) .build(); } } - return downlinkMsg; + return null; } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/DeviceProfileProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/DeviceProfileProcessor.java index 3f5fe6a8ed..817d35879d 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/DeviceProfileProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/DeviceProfileProcessor.java @@ -18,7 +18,6 @@ package org.thingsboard.server.service.edge.rpc.processor.device.profile; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; -import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; @@ -29,5 +28,6 @@ public interface DeviceProfileProcessor extends EdgeProcessor { ListenableFuture processDeviceProfileMsgFromEdge(TenantId tenantId, Edge edge, DeviceProfileUpdateMsg deviceProfileUpdateMsg); - DownlinkMsg convertDeviceProfileEventToDownlink(EdgeEvent edgeEvent, EdgeId edgeId, EdgeVersion edgeVersion); + DownlinkMsg convertDeviceProfileEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion); + } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/edge/EdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/edge/EdgeProcessor.java index f49c4b0a00..024147507a 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/edge/EdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/edge/EdgeProcessor.java @@ -48,21 +48,19 @@ public class EdgeProcessor extends BaseEdgeProcessor { public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { EdgeId edgeId = new EdgeId(edgeEvent.getEntityId()); - DownlinkMsg downlinkMsg = null; switch (edgeEvent.getAction()) { case ASSIGNED_TO_CUSTOMER, UNASSIGNED_FROM_CUSTOMER -> { - Edge edge = edgeService.findEdgeById(edgeEvent.getTenantId(), edgeId); + Edge edge = edgeCtx.getEdgeService().findEdgeById(edgeEvent.getTenantId(), edgeId); if (edge != null) { - EdgeConfiguration edgeConfigMsg = - edgeMsgConstructor.constructEdgeConfiguration(edge); - downlinkMsg = DownlinkMsg.newBuilder() + EdgeConfiguration edgeConfigMsg = edgeCtx.getEdgeMsgConstructor().constructEdgeConfiguration(edge); + return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .setEdgeConfiguration(edgeConfigMsg) .build(); } } } - return downlinkMsg; + return null; } public ListenableFuture processEdgeNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { @@ -70,9 +68,9 @@ public class EdgeProcessor extends BaseEdgeProcessor { EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()); EdgeId edgeId = new EdgeId(new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB())); switch (actionType) { - case ASSIGNED_TO_CUSTOMER: + case ASSIGNED_TO_CUSTOMER: { CustomerId customerId = JacksonUtil.fromString(edgeNotificationMsg.getBody(), CustomerId.class); - Edge edge = edgeService.findEdgeById(tenantId, edgeId); + Edge edge = edgeCtx.getEdgeService().findEdgeById(tenantId, edgeId); if (customerId != null && (edge == null || customerId.isNullUid())) { return Futures.immediateFuture(null); } @@ -82,7 +80,7 @@ public class EdgeProcessor extends BaseEdgeProcessor { PageLink pageLink = new PageLink(1000); PageData pageData; do { - pageData = userService.findCustomerUsers(tenantId, customerId, pageLink); + pageData = edgeCtx.getUserService().findCustomerUsers(tenantId, customerId, pageLink); if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { log.trace("[{}][{}][{}] user(s) are going to be added to edge.", tenantId, edge.getId(), pageData.getData().size()); for (User user : pageData.getData()) { @@ -94,15 +92,17 @@ public class EdgeProcessor extends BaseEdgeProcessor { } } while (pageData != null && pageData.hasNext()); return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService); - case UNASSIGNED_FROM_CUSTOMER: + } + case UNASSIGNED_FROM_CUSTOMER: { CustomerId customerIdToDelete = JacksonUtil.fromString(edgeNotificationMsg.getBody(), CustomerId.class); - edge = edgeService.findEdgeById(tenantId, edgeId); + Edge edge = edgeCtx.getEdgeService().findEdgeById(tenantId, edgeId); if (customerIdToDelete != null && (edge == null || customerIdToDelete.isNullUid())) { return Futures.immediateFuture(null); } return Futures.transformAsync(saveEdgeEvent(edge.getTenantId(), edge.getId(), EdgeEventType.EDGE, EdgeEventActionType.UNASSIGNED_FROM_CUSTOMER, edgeId, null), voids -> saveEdgeEvent(edge.getTenantId(), edge.getId(), EdgeEventType.CUSTOMER, EdgeEventActionType.DELETED, customerIdToDelete, null), dbCallbackExecutorService); + } default: return Futures.immediateFuture(null); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/BaseEntityViewProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/BaseEntityViewProcessor.java index 9c2b9a37bd..757f606f69 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/BaseEntityViewProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/BaseEntityViewProcessor.java @@ -16,18 +16,23 @@ package org.thingsboard.server.service.edge.rpc.processor.entityview; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.util.Pair; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.gen.edge.v1.EntityViewUpdateMsg; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @Slf4j public abstract class BaseEntityViewProcessor extends BaseEdgeProcessor { + @Autowired + private DataValidator entityViewValidator; + protected Pair saveOrUpdateEntityView(TenantId tenantId, EntityViewId entityViewId, EntityViewUpdateMsg entityViewUpdateMsg) { boolean created = false; boolean entityViewNameUpdated = false; @@ -35,7 +40,7 @@ public abstract class BaseEntityViewProcessor extends BaseEdgeProcessor { if (entityView == null) { throw new RuntimeException("[{" + tenantId + "}] entityViewUpdateMsg {" + entityViewUpdateMsg + "} cannot be converted to entity view"); } - EntityView entityViewById = entityViewService.findEntityViewById(tenantId, entityViewId); + EntityView entityViewById = edgeCtx.getEntityViewService().findEntityViewById(tenantId, entityViewId); if (entityViewById == null) { created = true; entityView.setId(null); @@ -43,7 +48,7 @@ public abstract class BaseEntityViewProcessor extends BaseEdgeProcessor { entityView.setId(entityViewId); } String entityViewName = entityView.getName(); - EntityView entityViewByName = entityViewService.findEntityViewByTenantIdAndName(tenantId, entityViewName); + EntityView entityViewByName = edgeCtx.getEntityViewService().findEntityViewByTenantIdAndName(tenantId, entityViewName); if (entityViewByName != null && !entityViewByName.getId().equals(entityViewId)) { entityViewName = entityViewName + "_" + StringUtils.randomAlphanumeric(15); log.warn("[{}] Entity view with name {} already exists. Renaming entity view name to {}", @@ -57,7 +62,7 @@ public abstract class BaseEntityViewProcessor extends BaseEdgeProcessor { if (created) { entityView.setId(entityViewId); } - entityViewService.saveEntityView(entityView, false); + edgeCtx.getEntityViewService().saveEntityView(entityView, false); return Pair.of(created, entityViewNameUpdated); } 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 4ef37f703b..aa105652d5 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 @@ -18,6 +18,7 @@ package org.thingsboard.server.service.edge.rpc.processor.entityview; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.util.Pair; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.EdgeUtils; @@ -36,12 +37,16 @@ import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.EntityViewUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.service.edge.rpc.constructor.entityview.EntityViewMsgConstructor; +import org.thingsboard.server.service.edge.rpc.constructor.entityview.EntityViewMsgConstructorFactory; import java.util.UUID; @Slf4j public abstract class EntityViewEdgeProcessor extends BaseEntityViewProcessor implements EntityViewProcessor { + @Autowired + private EntityViewMsgConstructorFactory entityViewMsgConstructorFactory; + @Override public ListenableFuture processEntityViewMsgFromEdge(TenantId tenantId, Edge edge, EntityViewUpdateMsg entityViewUpdateMsg) { log.trace("[{}] executing processEntityViewMsgFromEdge [{}] from edge [{}]", tenantId, entityViewUpdateMsg, edge.getId()); @@ -55,9 +60,9 @@ public abstract class EntityViewEdgeProcessor extends BaseEntityViewProcessor im saveOrUpdateEntityView(tenantId, entityViewId, entityViewUpdateMsg, edge); return Futures.immediateFuture(null); case ENTITY_DELETED_RPC_MESSAGE: - EntityView entityViewToDelete = entityViewService.findEntityViewById(tenantId, entityViewId); + EntityView entityViewToDelete = edgeCtx.getEntityViewService().findEntityViewById(tenantId, entityViewId); if (entityViewToDelete != null) { - entityViewService.unassignEntityViewFromEdge(tenantId, entityViewId, edge.getId()); + edgeCtx.getEntityViewService().unassignEntityViewFromEdge(tenantId, entityViewId, edge.getId()); } return Futures.immediateFuture(null); case UNRECOGNIZED: @@ -82,7 +87,7 @@ public abstract class EntityViewEdgeProcessor extends BaseEntityViewProcessor im if (created) { createRelationFromEdge(tenantId, edge.getId(), entityViewId); pushEntityViewCreatedEventToRuleEngine(tenantId, edge, entityViewId); - entityViewService.assignEntityViewToEdge(tenantId, entityViewId, edge.getId()); + edgeCtx.getEntityViewService().assignEntityViewToEdge(tenantId, entityViewId, edge.getId()); } Boolean assetNameUpdated = resultPair.getSecond(); if (assetNameUpdated) { @@ -92,7 +97,7 @@ public abstract class EntityViewEdgeProcessor extends BaseEntityViewProcessor im private void pushEntityViewCreatedEventToRuleEngine(TenantId tenantId, Edge edge, EntityViewId entityViewId) { try { - EntityView entityView = entityViewService.findEntityViewById(tenantId, entityViewId); + EntityView entityView = edgeCtx.getEntityViewService().findEntityViewById(tenantId, entityViewId); String entityViewAsString = JacksonUtil.toString(entityView); TbMsgMetaData msgMetaData = getEdgeActionTbMsgMetaData(edge, entityView.getCustomerId()); pushEntityEventToRuleEngine(tenantId, entityViewId, entityView.getCustomerId(), TbMsgType.ENTITY_CREATED, entityViewAsString, msgMetaData); @@ -103,26 +108,28 @@ public abstract class EntityViewEdgeProcessor extends BaseEntityViewProcessor im public DownlinkMsg convertEntityViewEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { EntityViewId entityViewId = new EntityViewId(edgeEvent.getEntityId()); - DownlinkMsg downlinkMsg = null; var msgConstructor = (EntityViewMsgConstructor) entityViewMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion); switch (edgeEvent.getAction()) { case ADDED, UPDATED, ASSIGNED_TO_EDGE, ASSIGNED_TO_CUSTOMER, UNASSIGNED_FROM_CUSTOMER -> { - EntityView entityView = entityViewService.findEntityViewById(edgeEvent.getTenantId(), entityViewId); + EntityView entityView = edgeCtx.getEntityViewService().findEntityViewById(edgeEvent.getTenantId(), entityViewId); if (entityView != null) { UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); EntityViewUpdateMsg entityViewUpdateMsg = msgConstructor.constructEntityViewUpdatedMsg(msgType, entityView); - downlinkMsg = DownlinkMsg.newBuilder() + return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addEntityViewUpdateMsg(entityViewUpdateMsg) .build(); } } - case DELETED, UNASSIGNED_FROM_EDGE -> downlinkMsg = DownlinkMsg.newBuilder() - .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) - .addEntityViewUpdateMsg(msgConstructor.constructEntityViewDeleteMsg(entityViewId)) - .build(); + case DELETED, UNASSIGNED_FROM_EDGE -> { + EntityViewUpdateMsg entityViewUpdateMsg = msgConstructor.constructEntityViewDeleteMsg(entityViewId); + return DownlinkMsg.newBuilder() + .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) + .addEntityViewUpdateMsg(entityViewUpdateMsg) + .build(); + } } - return downlinkMsg; + return null; } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/notification/NotificationEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/notification/NotificationEdgeProcessor.java index 6af039341f..67aa1dece2 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/notification/NotificationEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/notification/NotificationEdgeProcessor.java @@ -16,6 +16,7 @@ package org.thingsboard.server.service.edge.rpc.processor.notification; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.edge.EdgeEvent; @@ -25,12 +26,16 @@ import org.thingsboard.server.common.data.id.NotificationTemplateId; 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; +import org.thingsboard.server.dao.notification.NotificationRuleService; +import org.thingsboard.server.dao.notification.NotificationTargetService; +import org.thingsboard.server.dao.notification.NotificationTemplateService; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.NotificationRuleUpdateMsg; import org.thingsboard.server.gen.edge.v1.NotificationTargetUpdateMsg; import org.thingsboard.server.gen.edge.v1.NotificationTemplateUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.edge.rpc.constructor.notification.NotificationMsgConstructor; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @Slf4j @@ -38,6 +43,18 @@ import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @TbCoreComponent public class NotificationEdgeProcessor extends BaseEdgeProcessor { + @Autowired + protected NotificationRuleService notificationRuleService; + + @Autowired + private NotificationTargetService notificationTargetService; + + @Autowired + private NotificationTemplateService notificationTemplateService; + + @Autowired + private NotificationMsgConstructor notificationMsgConstructor; + public DownlinkMsg convertNotificationRuleToDownlink(EdgeEvent edgeEvent) { NotificationRuleId notificationRuleId = new NotificationRuleId(edgeEvent.getEntityId()); DownlinkMsg downlinkMsg = null; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/oauth2/OAuth2EdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/oauth2/OAuth2EdgeProcessor.java index b13cf567ef..9f4af7d0dc 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/oauth2/OAuth2EdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/oauth2/OAuth2EdgeProcessor.java @@ -16,6 +16,7 @@ package org.thingsboard.server.service.edge.rpc.processor.oauth2; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.domain.DomainInfo; @@ -23,12 +24,14 @@ import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.id.DomainId; import org.thingsboard.server.common.data.id.OAuth2ClientId; import org.thingsboard.server.common.data.oauth2.OAuth2Client; +import org.thingsboard.server.dao.domain.DomainService; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.OAuth2ClientUpdateMsg; import org.thingsboard.server.gen.edge.v1.OAuth2DomainUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.edge.rpc.constructor.oauth2.OAuth2MsgConstructor; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; import org.thingsboard.server.service.edge.rpc.utils.EdgeVersionUtils; @@ -37,12 +40,17 @@ import org.thingsboard.server.service.edge.rpc.utils.EdgeVersionUtils; @TbCoreComponent public class OAuth2EdgeProcessor extends BaseEdgeProcessor { + @Autowired + private DomainService domainService; + + @Autowired + private OAuth2MsgConstructor oAuth2MsgConstructor; + public DownlinkMsg convertOAuth2DomainEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { if (EdgeVersionUtils.isEdgeVersionOlderThan(edgeVersion, EdgeVersion.V_3_8_0)) { return null; } DomainId domainId = new DomainId(edgeEvent.getEntityId()); - DownlinkMsg downlinkMsg = null; switch (edgeEvent.getAction()) { case ADDED, UPDATED -> { @@ -54,22 +62,22 @@ public class OAuth2EdgeProcessor extends BaseEdgeProcessor { .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addOAuth2DomainUpdateMsg(oAuth2DomainUpdateMsg); domainInfo.getOauth2ClientInfos().forEach(clientInfo -> { - OAuth2Client oauth2Client = oAuth2ClientService.findOAuth2ClientById(edgeEvent.getTenantId(), clientInfo.getId()); + OAuth2Client oauth2Client = edgeCtx.getOAuth2ClientService().findOAuth2ClientById(edgeEvent.getTenantId(), clientInfo.getId()); OAuth2ClientUpdateMsg oAuth2ClientUpdateMsg = oAuth2MsgConstructor.constructOAuth2ClientUpdateMsg(msgType, oauth2Client); builder.addOAuth2ClientUpdateMsg(oAuth2ClientUpdateMsg); }); - downlinkMsg = builder.build(); + return builder.build(); } } case DELETED -> { OAuth2DomainUpdateMsg oAuth2DomainUpdateMsg = oAuth2MsgConstructor.constructOAuth2DomainDeleteMsg(domainId); - downlinkMsg = DownlinkMsg.newBuilder() + return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addOAuth2DomainUpdateMsg(oAuth2DomainUpdateMsg) .build(); } } - return downlinkMsg; + return null; } public DownlinkMsg convertOAuth2ClientEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { @@ -77,19 +85,18 @@ public class OAuth2EdgeProcessor extends BaseEdgeProcessor { return null; } OAuth2ClientId oAuth2ClientId = new OAuth2ClientId(edgeEvent.getEntityId()); - DownlinkMsg downlinkMsg = null; switch (edgeEvent.getAction()) { case ADDED, UPDATED -> { - boolean isPropagateToEdge = oAuth2ClientService.isPropagateOAuth2ClientToEdge(edgeEvent.getTenantId(), oAuth2ClientId); + boolean isPropagateToEdge = edgeCtx.getOAuth2ClientService().isPropagateOAuth2ClientToEdge(edgeEvent.getTenantId(), oAuth2ClientId); if (!isPropagateToEdge) { return null; } - OAuth2Client oAuth2Client = oAuth2ClientService.findOAuth2ClientById(edgeEvent.getTenantId(), oAuth2ClientId); + OAuth2Client oAuth2Client = edgeCtx.getOAuth2ClientService().findOAuth2ClientById(edgeEvent.getTenantId(), oAuth2ClientId); if (oAuth2Client != null) { UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); OAuth2ClientUpdateMsg oAuth2ClientUpdateMsg = oAuth2MsgConstructor.constructOAuth2ClientUpdateMsg(msgType, oAuth2Client); - downlinkMsg = DownlinkMsg.newBuilder() + return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addOAuth2ClientUpdateMsg(oAuth2ClientUpdateMsg) .build(); @@ -97,13 +104,13 @@ public class OAuth2EdgeProcessor extends BaseEdgeProcessor { } case DELETED -> { OAuth2ClientUpdateMsg oAuth2ClientDeleteMsg = oAuth2MsgConstructor.constructOAuth2ClientDeleteMsg(oAuth2ClientId); - downlinkMsg = DownlinkMsg.newBuilder() + return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addOAuth2ClientUpdateMsg(oAuth2ClientDeleteMsg) .build(); } } - return downlinkMsg; + return null; } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/ota/OtaPackageEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/ota/OtaPackageEdgeProcessor.java index 34a7724fee..fc803caf4d 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/ota/OtaPackageEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/ota/OtaPackageEdgeProcessor.java @@ -16,6 +16,7 @@ package org.thingsboard.server.service.edge.rpc.processor.ota; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.OtaPackage; @@ -27,6 +28,7 @@ import org.thingsboard.server.gen.edge.v1.OtaPackageUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.edge.rpc.constructor.ota.OtaPackageMsgConstructor; +import org.thingsboard.server.service.edge.rpc.constructor.ota.OtaPackageMsgConstructorFactory; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @Component @@ -34,32 +36,33 @@ import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @TbCoreComponent public class OtaPackageEdgeProcessor extends BaseEdgeProcessor { + @Autowired + private OtaPackageMsgConstructorFactory otaPackageMsgConstructorFactory; + public DownlinkMsg convertOtaPackageEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { OtaPackageId otaPackageId = new OtaPackageId(edgeEvent.getEntityId()); - DownlinkMsg downlinkMsg = null; + var msgConstructor = (OtaPackageMsgConstructor) otaPackageMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion); switch (edgeEvent.getAction()) { case ADDED, UPDATED -> { - OtaPackage otaPackage = otaPackageService.findOtaPackageById(edgeEvent.getTenantId(), otaPackageId); + OtaPackage otaPackage = edgeCtx.getOtaPackageService().findOtaPackageById(edgeEvent.getTenantId(), otaPackageId); if (otaPackage != null) { UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); - OtaPackageUpdateMsg otaPackageUpdateMsg = ((OtaPackageMsgConstructor) - otaPackageMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)).constructOtaPackageUpdatedMsg(msgType, otaPackage); - downlinkMsg = DownlinkMsg.newBuilder() + OtaPackageUpdateMsg otaPackageUpdateMsg = msgConstructor.constructOtaPackageUpdatedMsg(msgType, otaPackage); + return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addOtaPackageUpdateMsg(otaPackageUpdateMsg) .build(); } } case DELETED -> { - OtaPackageUpdateMsg otaPackageUpdateMsg = ((OtaPackageMsgConstructor) - otaPackageMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)).constructOtaPackageDeleteMsg(otaPackageId); - downlinkMsg = DownlinkMsg.newBuilder() + OtaPackageUpdateMsg otaPackageUpdateMsg = msgConstructor.constructOtaPackageDeleteMsg(otaPackageId); + return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addOtaPackageUpdateMsg(otaPackageUpdateMsg) .build(); } } - return downlinkMsg; + return null; } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/queue/QueueEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/queue/QueueEdgeProcessor.java index bfa81361fa..76f068ac7a 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/queue/QueueEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/queue/QueueEdgeProcessor.java @@ -16,6 +16,7 @@ package org.thingsboard.server.service.edge.rpc.processor.queue; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.edge.EdgeEvent; @@ -27,6 +28,7 @@ import org.thingsboard.server.gen.edge.v1.QueueUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.edge.rpc.constructor.queue.QueueMsgConstructor; +import org.thingsboard.server.service.edge.rpc.constructor.queue.QueueMsgConstructorFactory; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @Slf4j @@ -34,32 +36,33 @@ import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @TbCoreComponent public class QueueEdgeProcessor extends BaseEdgeProcessor { + @Autowired + private QueueMsgConstructorFactory queueMsgConstructorFactory; + public DownlinkMsg convertQueueEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { QueueId queueId = new QueueId(edgeEvent.getEntityId()); - DownlinkMsg downlinkMsg = null; + var msgConstructor = (QueueMsgConstructor) queueMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion); switch (edgeEvent.getAction()) { case ADDED, UPDATED -> { - Queue queue = queueService.findQueueById(edgeEvent.getTenantId(), queueId); + Queue queue = edgeCtx.getQueueService().findQueueById(edgeEvent.getTenantId(), queueId); if (queue != null) { UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); - QueueUpdateMsg queueUpdateMsg = ((QueueMsgConstructor) - queueMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)).constructQueueUpdatedMsg(msgType, queue); - downlinkMsg = DownlinkMsg.newBuilder() + QueueUpdateMsg queueUpdateMsg = msgConstructor.constructQueueUpdatedMsg(msgType, queue); + return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addQueueUpdateMsg(queueUpdateMsg) .build(); } } case DELETED -> { - QueueUpdateMsg queueDeleteMsg = ((QueueMsgConstructor) - queueMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)).constructQueueDeleteMsg(queueId); - downlinkMsg = DownlinkMsg.newBuilder() + QueueUpdateMsg queueDeleteMsg = msgConstructor.constructQueueDeleteMsg(queueId); + return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addQueueUpdateMsg(queueDeleteMsg) .build(); } } - return downlinkMsg; + return null; } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/BaseRelationProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/BaseRelationProcessor.java index a832f0b1e1..0c6da73a25 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/BaseRelationProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/BaseRelationProcessor.java @@ -36,15 +36,14 @@ public abstract class BaseRelationProcessor extends BaseEdgeProcessor { switch (relationUpdateMsg.getMsgType()) { case ENTITY_CREATED_RPC_MESSAGE: case ENTITY_UPDATED_RPC_MESSAGE: - if (isEntityExists(tenantId, entityRelation.getTo()) - && isEntityExists(tenantId, entityRelation.getFrom())) { - relationService.saveRelation(tenantId, entityRelation); + if (isEntityExists(tenantId, entityRelation.getTo()) && isEntityExists(tenantId, entityRelation.getFrom())) { + edgeCtx.getRelationService().saveRelation(tenantId, entityRelation); } else { log.warn("[{}] Skipping relating update msg because from/to entity doesn't exists on edge, {}", tenantId, relationUpdateMsg); } break; case ENTITY_DELETED_RPC_MESSAGE: - relationService.deleteRelation(tenantId, entityRelation); + edgeCtx.getRelationService().deleteRelation(tenantId, entityRelation); break; case UNRECOGNIZED: default: diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessor.java index 780550ed95..87b9797620 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessor.java @@ -18,6 +18,7 @@ package org.thingsboard.server.service.edge.rpc.processor.relation; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.EntityType; @@ -34,6 +35,7 @@ import org.thingsboard.server.gen.edge.v1.RelationUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.service.edge.rpc.constructor.relation.RelationMsgConstructor; +import org.thingsboard.server.service.edge.rpc.constructor.relation.RelationMsgConstructorFactory; import java.util.ArrayList; import java.util.HashSet; @@ -43,6 +45,9 @@ import java.util.Set; @Slf4j public abstract class RelationEdgeProcessor extends BaseRelationProcessor implements RelationProcessor { + @Autowired + private RelationMsgConstructorFactory relationMsgConstructorFactory; + @Override public ListenableFuture processRelationMsgFromEdge(TenantId tenantId, Edge edge, RelationUpdateMsg relationUpdateMsg) { log.trace("[{}] executing processRelationMsgFromEdge [{}] from edge [{}]", tenantId, relationUpdateMsg, edge.getId()); @@ -74,8 +79,8 @@ public abstract class RelationEdgeProcessor extends BaseRelationProcessor implem EdgeId originatorEdgeId = safeGetEdgeId(edgeNotificationMsg.getOriginatorEdgeIdMSB(), edgeNotificationMsg.getOriginatorEdgeIdLSB()); Set uniqueEdgeIds = new HashSet<>(); - uniqueEdgeIds.addAll(edgeService.findAllRelatedEdgeIds(tenantId, relation.getTo())); - uniqueEdgeIds.addAll(edgeService.findAllRelatedEdgeIds(tenantId, relation.getFrom())); + uniqueEdgeIds.addAll(edgeCtx.getEdgeService().findAllRelatedEdgeIds(tenantId, relation.getTo())); + uniqueEdgeIds.addAll(edgeCtx.getEdgeService().findAllRelatedEdgeIds(tenantId, relation.getFrom())); uniqueEdgeIds.remove(originatorEdgeId); if (uniqueEdgeIds.isEmpty()) { return Futures.immediateFuture(null); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/BaseResourceProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/BaseResourceProcessor.java index 7384130baf..ac7bd6768c 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/BaseResourceProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/BaseResourceProcessor.java @@ -17,6 +17,7 @@ package org.thingsboard.server.service.edge.rpc.processor.resource; import com.datastax.oss.driver.api.core.uuid.Uuids; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.TbResource; @@ -24,12 +25,16 @@ import org.thingsboard.server.common.data.TbResourceInfo; import org.thingsboard.server.common.data.id.TbResourceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageDataIterable; +import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.gen.edge.v1.ResourceUpdateMsg; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @Slf4j public abstract class BaseResourceProcessor extends BaseEdgeProcessor { + @Autowired + private DataValidator resourceValidator; + protected boolean saveOrUpdateTbResource(TenantId tenantId, TbResourceId tbResourceId, ResourceUpdateMsg resourceUpdateMsg) { boolean resourceKeyUpdated = false; try { @@ -38,7 +43,7 @@ public abstract class BaseResourceProcessor extends BaseEdgeProcessor { throw new RuntimeException("[{" + tenantId + "}] resourceUpdateMsg {" + resourceUpdateMsg + " } cannot be converted to resource"); } boolean created = false; - TbResource resourceById = resourceService.findResourceById(tenantId, tbResourceId); + TbResource resourceById = edgeCtx.getResourceService().findResourceById(tenantId, tbResourceId); if (resourceById == null) { resource.setCreatedTime(Uuids.unixTimestamp(tbResourceId.getId())); created = true; @@ -49,7 +54,7 @@ public abstract class BaseResourceProcessor extends BaseEdgeProcessor { String resourceKey = resource.getResourceKey(); ResourceType resourceType = resource.getResourceType(); PageDataIterable resourcesIterable = new PageDataIterable<>( - link -> resourceService.findTenantResourcesByResourceTypeAndPageLink(tenantId, resourceType, link), 1024); + link -> edgeCtx.getResourceService().findTenantResourcesByResourceTypeAndPageLink(tenantId, resourceType, link), 1024); for (TbResource tbResource : resourcesIterable) { if (tbResource.getResourceKey().equals(resourceKey) && !tbResourceId.equals(tbResource.getId())) { resourceKey = StringUtils.randomAlphabetic(15) + "_" + resourceKey; @@ -63,7 +68,7 @@ public abstract class BaseResourceProcessor extends BaseEdgeProcessor { if (created) { resource.setId(tbResourceId); } - resourceService.saveResource(resource, false); + edgeCtx.getResourceService().saveResource(resource, false); } catch (Exception e) { log.error("[{}] Failed to process resource update msg [{}]", tenantId, resourceUpdateMsg, e); throw e; 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 0a83e439b5..10c58e89d0 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 @@ -18,6 +18,7 @@ package org.thingsboard.server.service.edge.rpc.processor.resource; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.edge.Edge; @@ -32,12 +33,16 @@ import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.ResourceUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.service.edge.rpc.constructor.resource.ResourceMsgConstructor; +import org.thingsboard.server.service.edge.rpc.constructor.resource.ResourceMsgConstructorFactory; import java.util.UUID; @Slf4j public abstract class ResourceEdgeProcessor extends BaseResourceProcessor implements ResourceProcessor { + @Autowired + private ResourceMsgConstructorFactory resourceMsgConstructorFactory; + @Override public ListenableFuture processResourceMsgFromEdge(TenantId tenantId, Edge edge, ResourceUpdateMsg resourceUpdateMsg) { TbResourceId tbResourceId = new TbResourceId(new UUID(resourceUpdateMsg.getIdMSB(), resourceUpdateMsg.getIdLSB())); @@ -72,30 +77,28 @@ public abstract class ResourceEdgeProcessor extends BaseResourceProcessor implem @Override public DownlinkMsg convertResourceEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { TbResourceId tbResourceId = new TbResourceId(edgeEvent.getEntityId()); - DownlinkMsg downlinkMsg = null; + var msgConstructor = (ResourceMsgConstructor) resourceMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion); switch (edgeEvent.getAction()) { case ADDED, UPDATED -> { - TbResource tbResource = resourceService.findResourceById(edgeEvent.getTenantId(), tbResourceId); + TbResource tbResource = edgeCtx.getResourceService().findResourceById(edgeEvent.getTenantId(), tbResourceId); if (tbResource != null) { UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); - ResourceUpdateMsg resourceUpdateMsg = ((ResourceMsgConstructor) - resourceMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)).constructResourceUpdatedMsg(msgType, tbResource); - downlinkMsg = resourceUpdateMsg != null ? DownlinkMsg.newBuilder() + ResourceUpdateMsg resourceUpdateMsg = msgConstructor.constructResourceUpdatedMsg(msgType, tbResource); + return resourceUpdateMsg != null ? DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addResourceUpdateMsg(resourceUpdateMsg) .build() : null; } } case DELETED -> { - ResourceUpdateMsg resourceUpdateMsg = ((ResourceMsgConstructor) - resourceMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)).constructResourceDeleteMsg(tbResourceId); - downlinkMsg = DownlinkMsg.newBuilder() + ResourceUpdateMsg resourceUpdateMsg = msgConstructor.constructResourceDeleteMsg(tbResourceId); + return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addResourceUpdateMsg(resourceUpdateMsg) .build(); } } - return downlinkMsg; + return null; } } 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 310d8d9b9b..6df069e400 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 @@ -16,6 +16,7 @@ package org.thingsboard.server.service.edge.rpc.processor.rule; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.edge.Edge; @@ -30,6 +31,7 @@ import org.thingsboard.server.gen.edge.v1.RuleChainUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.edge.rpc.constructor.rule.RuleChainMsgConstructor; +import org.thingsboard.server.service.edge.rpc.constructor.rule.RuleChainMsgConstructorFactory; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; import static org.thingsboard.server.dao.edge.EdgeServiceImpl.EDGE_IS_ROOT_BODY_KEY; @@ -39,13 +41,16 @@ import static org.thingsboard.server.dao.edge.EdgeServiceImpl.EDGE_IS_ROOT_BODY_ @TbCoreComponent public class RuleChainEdgeProcessor extends BaseEdgeProcessor { + @Autowired + private RuleChainMsgConstructorFactory ruleChainMsgConstructorFactory; + public DownlinkMsg convertRuleChainEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { RuleChainId ruleChainId = new RuleChainId(edgeEvent.getEntityId()); DownlinkMsg downlinkMsg = null; var msgConstructor = (RuleChainMsgConstructor) ruleChainMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion); switch (edgeEvent.getAction()) { case ADDED, UPDATED, ASSIGNED_TO_EDGE -> { - RuleChain ruleChain = ruleChainService.findRuleChainById(edgeEvent.getTenantId(), ruleChainId); + RuleChain ruleChain = edgeCtx.getRuleChainService().findRuleChainById(edgeEvent.getTenantId(), ruleChainId); if (ruleChain != null) { boolean isRoot = false; if (edgeEvent.getBody() != null && edgeEvent.getBody().get(EDGE_IS_ROOT_BODY_KEY) != null) { @@ -55,7 +60,7 @@ public class RuleChainEdgeProcessor extends BaseEdgeProcessor { } } if (!isRoot) { - Edge edge = edgeService.findEdgeById(edgeEvent.getTenantId(), edgeEvent.getEdgeId()); + Edge edge = edgeCtx.getEdgeService().findEdgeById(edgeEvent.getTenantId(), edgeEvent.getEdgeId()); isRoot = edge.getRootRuleChainId().equals(ruleChainId); } UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); @@ -65,9 +70,8 @@ public class RuleChainEdgeProcessor extends BaseEdgeProcessor { .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addRuleChainUpdateMsg(ruleChainUpdateMsg); - RuleChainMetaData ruleChainMetaData = ruleChainService.loadRuleChainMetaData(edgeEvent.getTenantId(), ruleChainId); - RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg = ((RuleChainMsgConstructor) - ruleChainMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)) + RuleChainMetaData ruleChainMetaData = edgeCtx.getRuleChainService().loadRuleChainMetaData(edgeEvent.getTenantId(), ruleChainId); + RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg = msgConstructor .constructRuleChainMetadataUpdatedMsg(edgeEvent.getTenantId(), msgType, ruleChainMetaData, edgeVersion); if (ruleChainMetadataUpdateMsg != null) { builder.addRuleChainMetadataUpdateMsg(ruleChainMetadataUpdateMsg); @@ -85,22 +89,21 @@ public class RuleChainEdgeProcessor extends BaseEdgeProcessor { public DownlinkMsg convertRuleChainMetadataEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { RuleChainId ruleChainId = new RuleChainId(edgeEvent.getEntityId()); - RuleChain ruleChain = ruleChainService.findRuleChainById(edgeEvent.getTenantId(), ruleChainId); - DownlinkMsg downlinkMsg = null; + RuleChain ruleChain = edgeCtx.getRuleChainService().findRuleChainById(edgeEvent.getTenantId(), ruleChainId); var msgConstructor = (RuleChainMsgConstructor) ruleChainMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion); if (ruleChain != null) { - RuleChainMetaData ruleChainMetaData = ruleChainService.loadRuleChainMetaData(edgeEvent.getTenantId(), ruleChainId); + RuleChainMetaData ruleChainMetaData = edgeCtx.getRuleChainService().loadRuleChainMetaData(edgeEvent.getTenantId(), ruleChainId); UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg = msgConstructor .constructRuleChainMetadataUpdatedMsg(edgeEvent.getTenantId(), msgType, ruleChainMetaData, edgeVersion); if (ruleChainMetadataUpdateMsg != null) { - downlinkMsg = DownlinkMsg.newBuilder() + return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addRuleChainMetadataUpdateMsg(ruleChainMetadataUpdateMsg) .build(); } } - return downlinkMsg; + return null; } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/settings/AdminSettingsEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/settings/AdminSettingsEdgeProcessor.java index d9b0a85056..3c0d4fb99c 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/settings/AdminSettingsEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/settings/AdminSettingsEdgeProcessor.java @@ -16,6 +16,7 @@ package org.thingsboard.server.service.edge.rpc.processor.settings; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.AdminSettings; @@ -26,6 +27,7 @@ import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.edge.rpc.constructor.settings.AdminSettingsMsgConstructor; +import org.thingsboard.server.service.edge.rpc.constructor.settings.AdminSettingsMsgConstructorFactory; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @Slf4j @@ -33,6 +35,9 @@ import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @TbCoreComponent public class AdminSettingsEdgeProcessor extends BaseEdgeProcessor { + @Autowired + private AdminSettingsMsgConstructorFactory adminSettingsMsgConstructorFactory; + public DownlinkMsg convertAdminSettingsEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { AdminSettings adminSettings = JacksonUtil.convertValue(edgeEvent.getBody(), AdminSettings.class); if (adminSettings == null) { diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java index 2656ef622f..d94d4d8939 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java @@ -28,6 +28,8 @@ import jakarta.annotation.PostConstruct; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.adaptor.JsonConverter; import org.thingsboard.server.common.data.AttributeScope; @@ -66,7 +68,13 @@ import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.TbQueueMsgMetadata; import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.common.TbProtoQueueMsg; +import org.thingsboard.server.queue.discovery.PartitionService; +import org.thingsboard.server.queue.provider.TbQueueProducerProvider; +import org.thingsboard.server.service.edge.rpc.constructor.telemetry.EntityDataMsgConstructor; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; +import org.thingsboard.server.service.profile.TbAssetProfileCache; +import org.thingsboard.server.service.profile.TbDeviceProfileCache; +import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import java.util.ArrayList; import java.util.List; @@ -75,6 +83,25 @@ import java.util.UUID; @Slf4j public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor { + @Autowired + private EntityDataMsgConstructor entityDataMsgConstructor; + + @Autowired + private PartitionService partitionService; + + @Autowired + private TelemetrySubscriptionService tsSubService; + + @Autowired + private TbDeviceProfileCache deviceProfileCache; + + @Autowired + private TbAssetProfileCache assetProfileCache; + + @Lazy + @Autowired + private TbQueueProducerProvider producerProvider; + private final Gson gson = new Gson(); private TbQueueProducer> tbCoreMsgProducer; @@ -139,7 +166,7 @@ public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor { CustomerId customerId = null; switch (entityId.getEntityType()) { case DEVICE -> { - Device device = deviceService.findDeviceById(tenantId, new DeviceId(entityId.getId())); + Device device = edgeCtx.getDeviceService().findDeviceById(tenantId, new DeviceId(entityId.getId())); if (device != null) { customerId = device.getCustomerId(); metaData.putValue("deviceName", device.getName()); @@ -147,7 +174,7 @@ public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor { } } case ASSET -> { - Asset asset = assetService.findAssetById(tenantId, new AssetId(entityId.getId())); + Asset asset = edgeCtx.getAssetService().findAssetById(tenantId, new AssetId(entityId.getId())); if (asset != null) { customerId = asset.getCustomerId(); metaData.putValue("assetName", asset.getName()); @@ -155,7 +182,7 @@ public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor { } } case ENTITY_VIEW -> { - EntityView entityView = entityViewService.findEntityViewById(tenantId, new EntityViewId(entityId.getId())); + EntityView entityView = edgeCtx.getEntityViewService().findEntityViewById(tenantId, new EntityViewId(entityId.getId())); if (entityView != null) { customerId = entityView.getCustomerId(); metaData.putValue("entityViewName", entityView.getName()); @@ -163,7 +190,7 @@ public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor { } } case EDGE -> { - Edge edge = edgeService.findEdgeById(tenantId, new EdgeId(entityId.getId())); + Edge edge = edgeCtx.getEdgeService().findEdgeById(tenantId, new EdgeId(entityId.getId())); if (edge != null) { customerId = edge.getCustomerId(); metaData.putValue("edgeName", edge.getName()); @@ -182,7 +209,7 @@ public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor { metaData.putValue("ts", tsKv.getTs() + ""); var defaultQueueAndRuleChain = getDefaultQueueNameAndRuleChainId(tenantId, entityId); TbMsg tbMsg = TbMsg.newMsg(defaultQueueAndRuleChain.getKey(), TbMsgType.POST_TELEMETRY_REQUEST, entityId, customerId, metaData, gson.toJson(json), defaultQueueAndRuleChain.getValue(), null); - tbClusterService.pushMsgToRuleEngine(tenantId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() { + edgeCtx.getClusterService().pushMsgToRuleEngine(tenantId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() { @Override public void onSuccess(TbQueueMsgMetadata metadata) { futureToSet.set(null); @@ -226,7 +253,7 @@ public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor { JsonObject json = JsonUtils.getJsonObject(msg.getKvList()); var defaultQueueAndRuleChain = getDefaultQueueNameAndRuleChainId(tenantId, entityId); TbMsg tbMsg = TbMsg.newMsg(defaultQueueAndRuleChain.getKey(), TbMsgType.POST_ATTRIBUTES_REQUEST, entityId, customerId, metaData, gson.toJson(json), defaultQueueAndRuleChain.getValue(), null); - tbClusterService.pushMsgToRuleEngine(tenantId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() { + edgeCtx.getClusterService().pushMsgToRuleEngine(tenantId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() { @Override public void onSuccess(TbQueueMsgMetadata metadata) { futureToSet.set(null); @@ -256,7 +283,7 @@ public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor { var defaultQueueAndRuleChain = getDefaultQueueNameAndRuleChainId(tenantId, entityId); TbMsg tbMsg = TbMsg.newMsg(defaultQueueAndRuleChain.getKey(), TbMsgType.ATTRIBUTES_UPDATED, entityId, customerId, metaData, gson.toJson(json), defaultQueueAndRuleChain.getValue(), null); - tbClusterService.pushMsgToRuleEngine(tenantId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() { + edgeCtx.getClusterService().pushMsgToRuleEngine(tenantId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() { @Override public void onSuccess(TbQueueMsgMetadata metadata) { futureToSet.set(null); @@ -284,11 +311,11 @@ public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor { String scope = attributeDeleteMsg.getScope(); List attributeKeys = attributeDeleteMsg.getAttributeNamesList(); - ListenableFuture> removeAllFuture = attributesService.removeAll(tenantId, entityId, AttributeScope.valueOf(scope), attributeKeys); + ListenableFuture> removeAllFuture = edgeCtx.getAttributesService().removeAll(tenantId, entityId, AttributeScope.valueOf(scope), attributeKeys); return Futures.transformAsync(removeAllFuture, removeAttributes -> { if (EntityType.DEVICE.name().equals(entityType)) { SettableFuture futureToSet = SettableFuture.create(); - tbClusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete( + edgeCtx.getClusterService().pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete( tenantId, (DeviceId) entityId, scope, attributeKeys), new TbQueueCallback() { @Override public void onSuccess(TbQueueMsgMetadata metadata) { diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantEdgeProcessor.java index 515dccb987..86bddd8ea2 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantEdgeProcessor.java @@ -16,6 +16,7 @@ package org.thingsboard.server.service.edge.rpc.processor.tenant; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.Tenant; @@ -30,6 +31,7 @@ import org.thingsboard.server.gen.edge.v1.TenantUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.edge.rpc.constructor.tenant.TenantMsgConstructor; +import org.thingsboard.server.service.edge.rpc.constructor.tenant.TenantMsgConstructorFactory; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @Slf4j @@ -37,28 +39,27 @@ import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @TbCoreComponent public class TenantEdgeProcessor extends BaseEdgeProcessor { + @Autowired + private TenantMsgConstructorFactory tenantMsgConstructorFactory; + public DownlinkMsg convertTenantEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { TenantId tenantId = new TenantId(edgeEvent.getEntityId()); - DownlinkMsg downlinkMsg = null; + var msgConstructor = ((TenantMsgConstructor) tenantMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)); if (EdgeEventActionType.UPDATED.equals(edgeEvent.getAction())) { - Tenant tenant = tenantService.findTenantById(tenantId); + Tenant tenant = edgeCtx.getTenantService().findTenantById(tenantId); if (tenant != null) { UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); - TenantUpdateMsg tenantUpdateMsg = ((TenantMsgConstructor) - tenantMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)) - .constructTenantUpdateMsg(msgType, tenant); - TenantProfile tenantProfile = tenantProfileService.findTenantProfileById(tenantId, tenant.getTenantProfileId()); - TenantProfileUpdateMsg tenantProfileUpdateMsg = ((TenantMsgConstructor) - tenantMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)) - .constructTenantProfileUpdateMsg(msgType, tenantProfile, edgeVersion); - downlinkMsg = DownlinkMsg.newBuilder() + TenantUpdateMsg tenantUpdateMsg = msgConstructor.constructTenantUpdateMsg(msgType, tenant); + TenantProfile tenantProfile = edgeCtx.getTenantProfileService().findTenantProfileById(tenantId, tenant.getTenantProfileId()); + TenantProfileUpdateMsg tenantProfileUpdateMsg = msgConstructor.constructTenantProfileUpdateMsg(msgType, tenantProfile, edgeVersion); + return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addTenantUpdateMsg(tenantUpdateMsg) .addTenantProfileUpdateMsg(tenantProfileUpdateMsg) .build(); } } - return downlinkMsg; + return null; } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantProfileEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantProfileEdgeProcessor.java index 6c3bc1733f..07ba6039af 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantProfileEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantProfileEdgeProcessor.java @@ -16,6 +16,7 @@ package org.thingsboard.server.service.edge.rpc.processor.tenant; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.TenantProfile; @@ -28,6 +29,7 @@ import org.thingsboard.server.gen.edge.v1.TenantProfileUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.edge.rpc.constructor.tenant.TenantMsgConstructor; +import org.thingsboard.server.service.edge.rpc.constructor.tenant.TenantMsgConstructorFactory; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @Slf4j @@ -35,23 +37,24 @@ import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @TbCoreComponent public class TenantProfileEdgeProcessor extends BaseEdgeProcessor { + @Autowired + private TenantMsgConstructorFactory tenantMsgConstructorFactory; + public DownlinkMsg convertTenantProfileEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { TenantProfileId tenantProfileId = new TenantProfileId(edgeEvent.getEntityId()); - DownlinkMsg downlinkMsg = null; + var msgConstructor = ((TenantMsgConstructor) tenantMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)); if (EdgeEventActionType.UPDATED.equals(edgeEvent.getAction())) { - TenantProfile tenantProfile = tenantProfileService.findTenantProfileById(edgeEvent.getTenantId(), tenantProfileId); + TenantProfile tenantProfile = edgeCtx.getTenantProfileService().findTenantProfileById(edgeEvent.getTenantId(), tenantProfileId); if (tenantProfile != null) { UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); - TenantProfileUpdateMsg tenantProfileUpdateMsg = ((TenantMsgConstructor) - tenantMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)) - .constructTenantProfileUpdateMsg(msgType, tenantProfile, edgeVersion); - downlinkMsg = DownlinkMsg.newBuilder() + TenantProfileUpdateMsg tenantProfileUpdateMsg = msgConstructor.constructTenantProfileUpdateMsg(msgType, tenantProfile, edgeVersion); + return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addTenantProfileUpdateMsg(tenantProfileUpdateMsg) .build(); } } - return downlinkMsg; + return null; } } 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 cd072b4a1e..319812a33b 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 @@ -16,6 +16,7 @@ package org.thingsboard.server.service.edge.rpc.processor.user; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.User; @@ -28,6 +29,7 @@ import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UserCredentialsUpdateMsg; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.edge.rpc.constructor.user.UserMsgConstructor; +import org.thingsboard.server.service.edge.rpc.constructor.user.UserMsgConstructorFactory; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @Slf4j @@ -35,43 +37,45 @@ import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @TbCoreComponent public class UserEdgeProcessor extends BaseEdgeProcessor { + @Autowired + private UserMsgConstructorFactory userMsgConstructorFactory; + public DownlinkMsg convertUserEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { UserId userId = new UserId(edgeEvent.getEntityId()); - DownlinkMsg downlinkMsg = null; + var msgConstructor = (UserMsgConstructor) userMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion); switch (edgeEvent.getAction()) { case ADDED, UPDATED -> { - User user = userService.findUserById(edgeEvent.getTenantId(), userId); + User user = edgeCtx.getUserService().findUserById(edgeEvent.getTenantId(), userId); if (user != null) { UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); DownlinkMsg.Builder builder = DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) - .addUserUpdateMsg(((UserMsgConstructor) userMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)).constructUserUpdatedMsg(msgType, user)); - UserCredentials userCredentialsByUserId = userService.findUserCredentialsByUserId(edgeEvent.getTenantId(), userId); + .addUserUpdateMsg(msgConstructor.constructUserUpdatedMsg(msgType, user)); + UserCredentials userCredentialsByUserId = edgeCtx.getUserService().findUserCredentialsByUserId(edgeEvent.getTenantId(), userId); if (userCredentialsByUserId != null && userCredentialsByUserId.isEnabled()) { - UserCredentialsUpdateMsg userCredentialsUpdateMsg = - ((UserMsgConstructor) userMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)).constructUserCredentialsUpdatedMsg(userCredentialsByUserId); - builder.addUserCredentialsUpdateMsg(userCredentialsUpdateMsg); + builder.addUserCredentialsUpdateMsg(msgConstructor.constructUserCredentialsUpdatedMsg(userCredentialsByUserId)); } - downlinkMsg = builder.build(); + return builder.build(); } } - case DELETED -> downlinkMsg = DownlinkMsg.newBuilder() - .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) - .addUserUpdateMsg(((UserMsgConstructor) userMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)).constructUserDeleteMsg(userId)) - .build(); + case DELETED -> { + return DownlinkMsg.newBuilder() + .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) + .addUserUpdateMsg(msgConstructor.constructUserDeleteMsg(userId)) + .build(); + } case CREDENTIALS_UPDATED -> { - UserCredentials userCredentialsByUserId = userService.findUserCredentialsByUserId(edgeEvent.getTenantId(), userId); + UserCredentials userCredentialsByUserId = edgeCtx.getUserService().findUserCredentialsByUserId(edgeEvent.getTenantId(), userId); if (userCredentialsByUserId != null && userCredentialsByUserId.isEnabled()) { - UserCredentialsUpdateMsg userCredentialsUpdateMsg = - ((UserMsgConstructor) userMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)).constructUserCredentialsUpdatedMsg(userCredentialsByUserId); - downlinkMsg = DownlinkMsg.newBuilder() + UserCredentialsUpdateMsg userCredentialsUpdateMsg = msgConstructor.constructUserCredentialsUpdatedMsg(userCredentialsByUserId); + return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addUserCredentialsUpdateMsg(userCredentialsUpdateMsg) .build(); } } } - return downlinkMsg; + return null; } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetBundleEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetBundleEdgeProcessor.java index 662b3677cc..87eece2841 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetBundleEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetBundleEdgeProcessor.java @@ -16,6 +16,7 @@ package org.thingsboard.server.service.edge.rpc.processor.widget; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.edge.EdgeEvent; @@ -27,6 +28,7 @@ import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.WidgetsBundleUpdateMsg; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.edge.rpc.constructor.widget.WidgetMsgConstructor; +import org.thingsboard.server.service.edge.rpc.constructor.widget.WidgetMsgConstructorFactory; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; import java.util.List; @@ -36,33 +38,34 @@ import java.util.List; @TbCoreComponent public class WidgetBundleEdgeProcessor extends BaseEdgeProcessor { + @Autowired + private WidgetMsgConstructorFactory widgetMsgConstructorFactory; + public DownlinkMsg convertWidgetsBundleEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { WidgetsBundleId widgetsBundleId = new WidgetsBundleId(edgeEvent.getEntityId()); - DownlinkMsg downlinkMsg = null; + var msgConstructor = ((WidgetMsgConstructor) widgetMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)); switch (edgeEvent.getAction()) { case ADDED, UPDATED -> { - WidgetsBundle widgetsBundle = widgetsBundleService.findWidgetsBundleById(edgeEvent.getTenantId(), widgetsBundleId); + WidgetsBundle widgetsBundle = edgeCtx.getWidgetsBundleService().findWidgetsBundleById(edgeEvent.getTenantId(), widgetsBundleId); if (widgetsBundle != null) { - List widgets = widgetTypeService.findWidgetFqnsByWidgetsBundleId(edgeEvent.getTenantId(), widgetsBundleId); + List widgets = edgeCtx.getWidgetTypeService().findWidgetFqnsByWidgetsBundleId(edgeEvent.getTenantId(), widgetsBundleId); UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); - WidgetsBundleUpdateMsg widgetsBundleUpdateMsg = - ((WidgetMsgConstructor) widgetMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)).constructWidgetsBundleUpdateMsg(msgType, widgetsBundle, widgets); - downlinkMsg = DownlinkMsg.newBuilder() + WidgetsBundleUpdateMsg widgetsBundleUpdateMsg = msgConstructor.constructWidgetsBundleUpdateMsg(msgType, widgetsBundle, widgets); + return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addWidgetsBundleUpdateMsg(widgetsBundleUpdateMsg) .build(); } } case DELETED -> { - WidgetsBundleUpdateMsg widgetsBundleUpdateMsg = - ((WidgetMsgConstructor) widgetMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)).constructWidgetsBundleDeleteMsg(widgetsBundleId); - downlinkMsg = DownlinkMsg.newBuilder() + WidgetsBundleUpdateMsg widgetsBundleUpdateMsg = msgConstructor.constructWidgetsBundleDeleteMsg(widgetsBundleId); + return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addWidgetsBundleUpdateMsg(widgetsBundleUpdateMsg) .build(); } } - return downlinkMsg; + return null; } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetTypeEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetTypeEdgeProcessor.java index c1891b8fdb..10d2ce4e82 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetTypeEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetTypeEdgeProcessor.java @@ -16,6 +16,7 @@ package org.thingsboard.server.service.edge.rpc.processor.widget; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.edge.EdgeEvent; @@ -27,6 +28,7 @@ import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.WidgetTypeUpdateMsg; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.edge.rpc.constructor.widget.WidgetMsgConstructor; +import org.thingsboard.server.service.edge.rpc.constructor.widget.WidgetMsgConstructorFactory; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @Slf4j @@ -34,32 +36,33 @@ import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @TbCoreComponent public class WidgetTypeEdgeProcessor extends BaseEdgeProcessor { + @Autowired + private WidgetMsgConstructorFactory widgetMsgConstructorFactory; + public DownlinkMsg convertWidgetTypeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { WidgetTypeId widgetTypeId = new WidgetTypeId(edgeEvent.getEntityId()); - DownlinkMsg downlinkMsg = null; + var msgConstructor = (WidgetMsgConstructor) widgetMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion); switch (edgeEvent.getAction()) { case ADDED, UPDATED -> { - WidgetTypeDetails widgetTypeDetails = widgetTypeService.findWidgetTypeDetailsById(edgeEvent.getTenantId(), widgetTypeId); + WidgetTypeDetails widgetTypeDetails = edgeCtx.getWidgetTypeService().findWidgetTypeDetailsById(edgeEvent.getTenantId(), widgetTypeId); if (widgetTypeDetails != null) { UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); - WidgetTypeUpdateMsg widgetTypeUpdateMsg = - ((WidgetMsgConstructor) widgetMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)).constructWidgetTypeUpdateMsg(msgType, widgetTypeDetails, edgeVersion); - downlinkMsg = DownlinkMsg.newBuilder() + WidgetTypeUpdateMsg widgetTypeUpdateMsg = msgConstructor.constructWidgetTypeUpdateMsg(msgType, widgetTypeDetails, edgeVersion); + return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addWidgetTypeUpdateMsg(widgetTypeUpdateMsg) .build(); } } case DELETED -> { - WidgetTypeUpdateMsg widgetTypeUpdateMsg = - ((WidgetMsgConstructor) widgetMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)).constructWidgetTypeDeleteMsg(widgetTypeId); - downlinkMsg = DownlinkMsg.newBuilder() + WidgetTypeUpdateMsg widgetTypeUpdateMsg = msgConstructor.constructWidgetTypeDeleteMsg(widgetTypeId); + return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addWidgetTypeUpdateMsg(widgetTypeUpdateMsg) .build(); } } - return downlinkMsg; + return null; } } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java b/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java index 863be23e42..bce599c0e3 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java @@ -57,13 +57,13 @@ import org.thingsboard.server.dao.tenant.TenantService; import java.util.Set; +@Slf4j @Component @RequiredArgsConstructor -@Slf4j public class EntityStateSourcingListener { - private final TbClusterService tbClusterService; private final TenantService tenantService; + private final TbClusterService tbClusterService; @PostConstruct public void init() { @@ -137,7 +137,7 @@ public class EntityStateSourcingListener { log.debug("[{}][{}][{}] Handling entity deletion event: {}", tenantId, entityType, entityId, event); switch (entityType) { - case ASSET, ASSET_PROFILE, ENTITY_VIEW, CUSTOMER, EDGE, NOTIFICATION_RULE -> { + case ASSET, ASSET_PROFILE, EDGE, ENTITY_VIEW, CUSTOMER, NOTIFICATION_RULE -> { tbClusterService.broadcastEntityStateChangeEvent(tenantId, entityId, ComponentLifecycleEvent.DELETED); } case NOTIFICATION_REQUEST -> { diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java index 4e9e8e954c..693f70cc9d 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java @@ -35,8 +35,6 @@ import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.AbstractTbEntityService; import org.thingsboard.server.service.security.system.SystemSecurityService; -import java.util.concurrent.TimeUnit; - @Service @TbCoreComponent @AllArgsConstructor @@ -90,16 +88,10 @@ public class DefaultUserService extends AbstractTbEntityService implements TbUse public UserActivationLink getActivationLink(TenantId tenantId, CustomerId customerId, UserId userId, HttpServletRequest request) throws ThingsboardException { UserCredentials userCredentials = userService.findUserCredentialsByUserId(tenantId, userId); if (!userCredentials.isEnabled() && userCredentials.getActivateToken() != null) { - long ttl = userCredentials.getActivationTokenTtl(); - if (ttl < TimeUnit.MINUTES.toMillis(15)) { // renew link if less than 15 minutes before expiration - userCredentials = userService.generateUserActivationToken(userCredentials); - userCredentials = userService.saveUserCredentials(tenantId, userCredentials); - ttl = userCredentials.getActivationTokenTtl(); - log.debug("[{}][{}] Regenerated expired user activation token", tenantId, userId); - } + userCredentials = userService.checkUserActivationToken(tenantId, userCredentials); String baseUrl = systemSecurityService.getBaseUrl(tenantId, customerId, request); String link = baseUrl + "/api/noauth/activate?activateToken=" + userCredentials.getActivateToken(); - return new UserActivationLink(link, ttl); + return new UserActivationLink(link, userCredentials.getActivationTokenTtl()); } else { throw new ThingsboardException("User is already activated!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); } diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbEdgeConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbEdgeConsumerService.java index 805aeb0bdb..f219d7ae69 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbEdgeConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbEdgeConsumerService.java @@ -104,7 +104,7 @@ public class DefaultTbEdgeConsumerService extends AbstractConsumerService, EdgeQueueConfig>builder() .queueKey(new QueueKey(ServiceType.TB_CORE).withQueueName(DataConstants.EDGE_QUEUE_NAME)) - .config(EdgeQueueConfig.of(consumerPerPartition, (int) pollInterval)) + .config(EdgeQueueConfig.of(consumerPerPartition, pollInterval)) .msgPackProcessor(this::processMsgs) .consumerCreator((config, partitionId) -> queueFactory.createEdgeMsgConsumer()) .consumerExecutor(consumersExecutor) diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/AbstractCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/AbstractCleanUpService.java index f435be07ff..3eb8348fae 100644 --- a/application/src/main/java/org/thingsboard/server/service/ttl/AbstractCleanUpService.java +++ b/application/src/main/java/org/thingsboard/server/service/ttl/AbstractCleanUpService.java @@ -28,7 +28,8 @@ public abstract class AbstractCleanUpService { private final PartitionService partitionService; - protected boolean isSystemTenantPartitionMine(){ + protected boolean isSystemTenantPartitionMine() { return partitionService.resolve(ServiceType.TB_CORE, TenantId.SYS_TENANT_ID, TenantId.SYS_TENANT_ID).isMyPartition(); } + } diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/EdgeEventsCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/EdgeEventsCleanUpService.java index 7c025d8703..8aca8f88b0 100644 --- a/application/src/main/java/org/thingsboard/server/service/ttl/EdgeEventsCleanUpService.java +++ b/application/src/main/java/org/thingsboard/server/service/ttl/EdgeEventsCleanUpService.java @@ -20,6 +20,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; +import org.thingsboard.server.dao.edge.EdgeEventDao; import org.thingsboard.server.dao.edge.EdgeEventService; import org.thingsboard.server.dao.sqlts.insert.sql.SqlPartitioningRepository; import org.thingsboard.server.queue.discovery.PartitionService; @@ -32,7 +33,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.EDGE_EVENT_TABLE_N @TbCoreComponent @Slf4j @Service -@ConditionalOnExpression("${sql.ttl.edge_events.enabled:true} && ${sql.ttl.edge_events.edge_events_ttl:0} > 0") +@ConditionalOnExpression("${edges.enabled:true} && ${sql.ttl.edge_events.edge_events_ttl:0} > 0") public class EdgeEventsCleanUpService extends AbstractCleanUpService { public static final String RANDOM_DELAY_INTERVAL_MS_EXPRESSION = diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/KafkaEdgeTopicsCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/KafkaEdgeTopicsCleanUpService.java new file mode 100644 index 0000000000..b28664e3be --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ttl/KafkaEdgeTopicsCleanUpService.java @@ -0,0 +1,160 @@ +/** + * Copyright © 2016-2024 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.ttl; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.AttributeScope; +import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.common.data.id.EdgeId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.dao.attributes.AttributesService; +import org.thingsboard.server.dao.edge.EdgeService; +import org.thingsboard.server.dao.tenant.TenantService; +import org.thingsboard.server.queue.discovery.PartitionService; +import org.thingsboard.server.queue.discovery.TopicService; +import org.thingsboard.server.queue.kafka.TbKafkaAdmin; +import org.thingsboard.server.queue.kafka.TbKafkaSettings; +import org.thingsboard.server.queue.kafka.TbKafkaTopicConfigs; +import org.thingsboard.server.queue.util.TbCoreComponent; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +import static org.thingsboard.server.service.state.DefaultDeviceStateService.LAST_CONNECT_TIME; + +@Slf4j +@Service +@TbCoreComponent +@ConditionalOnExpression("'${queue.type:null}'=='kafka' && ${edges.enabled:true} && ${sql.ttl.edge_events.edge_events_ttl:0} > 0") +public class KafkaEdgeTopicsCleanUpService extends AbstractCleanUpService { + + private static final String EDGE_EVENT_TOPIC_NAME = "tb_edge_event.notifications."; + + private final TopicService topicService; + private final TenantService tenantService; + private final EdgeService edgeService; + private final AttributesService attributesService; + private final TbKafkaAdmin kafkaAdmin; + + @Value("${sql.ttl.edge_events.edge_events_ttl:2628000}") + private long ttlSeconds; + + public KafkaEdgeTopicsCleanUpService(PartitionService partitionService, EdgeService edgeService, + TenantService tenantService, AttributesService attributesService, + TopicService topicService, TbKafkaSettings kafkaSettings, TbKafkaTopicConfigs kafkaTopicConfigs) { + super(partitionService); + this.topicService = topicService; + this.tenantService = tenantService; + this.edgeService = edgeService; + this.attributesService = attributesService; + this.kafkaAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getEdgeEventConfigs()); + } + + @Scheduled(initialDelayString = "#{T(org.apache.commons.lang3.RandomUtils).nextLong(0, ${sql.ttl.edge_events.execution_interval_ms})}", fixedDelayString = "${sql.ttl.edge_events.execution_interval_ms}") + public void cleanUp() { + if (!isSystemTenantPartitionMine()) { + return; + } + + Set topics = kafkaAdmin.getAllTopics(); + if (topics == null || topics.isEmpty()) { + return; + } + + String edgeTopicPrefix = topicService.buildTopicName(EDGE_EVENT_TOPIC_NAME); + List matchingTopics = topics.stream().filter(topic -> topic.startsWith(edgeTopicPrefix)).toList(); + if (matchingTopics.isEmpty()) { + log.debug("No matching topics found with prefix [{}]. Skipping cleanup.", edgeTopicPrefix); + return; + } + + Map> tenantEdgeMap = extractTenantAndEdgeIds(matchingTopics, edgeTopicPrefix); + + long currentTimeMillis = System.currentTimeMillis(); + long ttlMillis = TimeUnit.SECONDS.toMillis(ttlSeconds); + + tenantEdgeMap.forEach((tenantId, edgeIds) -> processTenantCleanUp(tenantId, edgeIds, ttlMillis, currentTimeMillis)); + } + + private void processTenantCleanUp(TenantId tenantId, List edgeIds, long ttlMillis, long currentTimeMillis) { + boolean tenantExists = tenantService.tenantExists(tenantId); + if (tenantExists) { + for (EdgeId edgeId : edgeIds) { + try { + attributesService.find(tenantId, edgeId, AttributeScope.SERVER_SCOPE, LAST_CONNECT_TIME).get() + .flatMap(AttributeKvEntry::getLongValue) + .filter(lastConnectTime -> isTopicExpired(lastConnectTime, ttlMillis, currentTimeMillis)) + .ifPresentOrElse(lastConnectTime -> { + String topic = topicService.buildEdgeEventNotificationsTopicPartitionInfo(tenantId, edgeId).getTopic(); + if (kafkaAdmin.isTopicEmpty(topic)) { + kafkaAdmin.deleteTopic(topic); + log.info("[{}] Removed outdated topic {} for edge {} older than {}", + tenantId, topic, edgeId, Date.from(Instant.ofEpochMilli(currentTimeMillis - ttlMillis))); + } + }, () -> { + Edge edge = edgeService.findEdgeById(tenantId, edgeId); + if (edge == null) { + String topic = topicService.buildEdgeEventNotificationsTopicPartitionInfo(tenantId, edgeId).getTopic(); + kafkaAdmin.deleteTopic(topic); + log.info("[{}] Removed topic {} for deleted edge {}", tenantId, topic, edgeId); + } + }); + } catch (Exception e) { + log.error("[{}] Failed to delete topic for edge {}", tenantId, edgeId, e); + } + } + } else { + for (EdgeId edgeId : edgeIds) { + String topic = topicService.buildEdgeEventNotificationsTopicPartitionInfo(tenantId, edgeId).getTopic(); + kafkaAdmin.deleteTopic(topic); + } + log.info("[{}] Removed topics for not existing tenant and edges {}", tenantId, edgeIds); + } + } + + private boolean isTopicExpired(long lastConnectTime, long ttlMillis, long currentTimeMillis) { + return lastConnectTime + ttlMillis < currentTimeMillis; + } + + private Map> extractTenantAndEdgeIds(List topics, String prefix) { + Map> tenantEdgeMap = new HashMap<>(); + for (String topic : topics) { + try { + String remaining = topic.substring(prefix.length()); + String[] parts = remaining.split("\\."); + TenantId tenantId = new TenantId(UUID.fromString(parts[0])); + EdgeId edgeId = new EdgeId(UUID.fromString(parts[1])); + tenantEdgeMap.computeIfAbsent(tenantId, id -> new ArrayList<>()).add(edgeId); + } catch (Exception e) { + log.warn("Failed to extract TenantId and EdgeId from topic [{}]", topic, e); + } + } + return tenantEdgeMap; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/update/DeprecationService.java b/application/src/main/java/org/thingsboard/server/service/update/DeprecationService.java index d4109ff420..ff1f013953 100644 --- a/application/src/main/java/org/thingsboard/server/service/update/DeprecationService.java +++ b/application/src/main/java/org/thingsboard/server/service/update/DeprecationService.java @@ -17,6 +17,7 @@ package org.thingsboard.server.service.update; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.thingsboard.rule.engine.api.NotificationCenter; @@ -24,6 +25,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.info.GeneralNotificationInfo; import org.thingsboard.server.common.data.notification.targets.platform.SystemAdministratorsFilter; import org.thingsboard.server.dao.notification.DefaultNotifications; +import org.thingsboard.server.dao.notification.DefaultNotifications.DefaultNotification; import org.thingsboard.server.queue.util.AfterStartUp; import java.util.Map; @@ -38,9 +40,16 @@ public class DeprecationService { @Value("${queue.type}") private String queueType; + @Value("${database.ts.type}") + private String tsType; + + @Value("${database.ts_latest.type}") + private String tsLatestType; + @AfterStartUp(order = Integer.MAX_VALUE) public void checkDeprecation() { checkQueueTypeDeprecation(); + checkDatabaseTypeDeprecation(); } private void checkQueueTypeDeprecation() { @@ -57,10 +66,25 @@ public class DeprecationService { log.warn("WARNING: Starting with ThingsBoard 4.0, {} will no longer be supported as a message queue for microservices. " + "Please migrate to Apache Kafka. This change will not impact any rule nodes", queueTypeName); + sendNotification(DefaultNotifications.queueTypeDeprecation, Map.of( + "queueType", queueTypeName + )); + } + + private void checkDatabaseTypeDeprecation() { + String deprecatedDatabaseType = "Timescale"; + if (StringUtils.equalsAnyIgnoreCase(deprecatedDatabaseType, tsType, tsLatestType)) { + log.warn("WARNING: Starting with ThingsBoard 4.0, {} will no longer be supported as a storage provider. " + + "Please migrate to Cassandra or PostgreSQL.", deprecatedDatabaseType); + sendNotification(DefaultNotifications.databaseTypeDeprecation, Map.of( + "databaseType", deprecatedDatabaseType + )); + } + } + + private void sendNotification(DefaultNotification notification, Map info) { notificationCenter.sendGeneralWebNotification(TenantId.SYS_TENANT_ID, new SystemAdministratorsFilter(), - DefaultNotifications.queueTypeDeprecation.toTemplate(), new GeneralNotificationInfo(Map.of( - "queueType", queueTypeName - ))); + notification.toTemplate(), new GeneralNotificationInfo(info)); } } diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 2a121e2626..a5e3532258 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -212,9 +212,9 @@ ui: database: ts_max_intervals: "${DATABASE_TS_MAX_INTERVALS:700}" # Max number of DB queries generated by a single API call to fetch telemetry records ts: - type: "${DATABASE_TS_TYPE:sql}" # cassandra, sql, or timescale (for hybrid mode, DATABASE_TS_TYPE value should be cassandra, or timescale) + type: "${DATABASE_TS_TYPE:sql}" # cassandra or sql. timescale option is deprecated and will no longer be supported in ThingsBoard 4.0 ts_latest: - type: "${DATABASE_TS_LATEST_TYPE:sql}" # cassandra, sql, or timescale (for hybrid mode, DATABASE_TS_TYPE value should be cassandra, or timescale) + type: "${DATABASE_TS_LATEST_TYPE:sql}" # cassandra or sql. timescale option is deprecated and will no longer be supported in ThingsBoard 4.0 # Cassandra driver configuration parameters cassandra: @@ -486,6 +486,17 @@ actors: submit_strategy: "${ACTORS_RPC_SUBMIT_STRATEGY_TYPE:BURST}" # Time in milliseconds for RPC to receive a response after delivery. Used only for SEQUENTIAL_ON_RESPONSE_FROM_DEVICE submit strategy. response_timeout_ms: "${ACTORS_RPC_RESPONSE_TIMEOUT_MS:30000}" + # Close transport session if RPC delivery timed out. If enabled, RPC will be reverted to the queued state. + # Note: + # - For MQTT transport: + # - QoS level 0: This feature does not apply, as no acknowledgment is expected, and therefore no timeout is triggered. + # - QoS level 1: This feature applies, as an acknowledgment is expected. + # - QoS level 2: Unsupported. + # - For CoAP transport: + # - Confirmable requests: This feature applies, as delivery confirmation is expected. + # - Non-confirmable requests: This feature does not apply, as no delivery acknowledgment is expected. + # - For HTTP and SNPM transports: RPC is considered delivered immediately, and there is no logic to await acknowledgment. + close_session_on_rpc_delivery_timeout: "${ACTORS_RPC_CLOSE_SESSION_ON_RPC_DELIVERY_TIMEOUT:false}" statistics: # Enable/disable actor statistics enabled: "${ACTORS_STATISTICS_ENABLED:true}" @@ -1532,6 +1543,11 @@ queue: # tb_rule_engine.sq: # - key: max.poll.records # value: "${TB_QUEUE_KAFKA_SQ_MAX_POLL_RECORDS:1024}" + tb_edge_event.notifications: + # Example of specific consumer properties value per topic for edge event + - key: max.poll.records + # Example of specific consumer properties value per topic for edge event + value: "${TB_QUEUE_KAFKA_EDGE_EVENT_MAX_POLL_RECORDS:50}" tb_housekeeper: # Consumer properties for Housekeeper tasks topic - key: max.poll.records @@ -1569,6 +1585,8 @@ queue: housekeeper-reprocessing: "${TB_QUEUE_KAFKA_HOUSEKEEPER_REPROCESSING_TOPIC_PROPERTIES:retention.ms:7776000000;segment.bytes:52428800;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" # Kafka properties for Edge topic edge: "${TB_QUEUE_KAFKA_EDGE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:52428800;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" + # Kafka properties for Edge event topic + edge-event: "${TB_QUEUE_KAFKA_EDGE_EVENT_TOPIC_PROPERTIES:retention.ms:2592000000;segment.bytes:52428800;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" consumer-stats: # Prints lag between consumer group offset and last messages offset in Kafka topics enabled: "${TB_QUEUE_KAFKA_CONSUMER_STATS_ENABLED:true}" 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 5484c29305..46a5d81d7f 100644 --- a/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java @@ -125,7 +125,7 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest { protected EdgeImitator edgeImitator; protected Edge edge; - private Random random = new Random(); + private final Random random = new Random(); @Autowired protected EdgeEventService edgeEventService; diff --git a/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java b/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java index 9e36fed677..143c6c72a1 100644 --- a/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java @@ -17,6 +17,7 @@ package org.thingsboard.server.rules.flow; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; +import org.awaitility.Awaitility; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -59,6 +60,7 @@ import org.thingsboard.server.dao.event.EventService; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import static org.mockito.Mockito.spy; @@ -331,6 +333,15 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule RuleChain finalRuleChain = rootRuleChain; RuleNode lastRuleNode = secondaryMetaData.getNodes().stream().filter(node -> !node.getId().equals(finalRuleChain.getFirstRuleNodeId())).findFirst().get(); + Awaitility.await().atMost(TIMEOUT, TimeUnit.SECONDS) + .until(() -> + getDebugEvents(savedTenant.getId(), lastRuleNode.getId(), 1000) + .getData() + .stream() + .filter(filterByPostTelemetryEventType()) + .count() == 2 + ); + eventsPage = getDebugEvents(savedTenant.getId(), lastRuleNode.getId(), 1000); events = eventsPage.getData().stream().filter(filterByPostTelemetryEventType()).collect(Collectors.toList()); diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv5/rpc/MqttV5CloseTransportSessionOnRpcDeliveryTimeoutIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv5/rpc/MqttV5CloseTransportSessionOnRpcDeliveryTimeoutIntegrationTest.java new file mode 100644 index 0000000000..dc366c11f2 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv5/rpc/MqttV5CloseTransportSessionOnRpcDeliveryTimeoutIntegrationTest.java @@ -0,0 +1,157 @@ +/** + * Copyright © 2016-2024 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.transport.mqtt.mqttv5.rpc; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.NullNode; +import io.netty.handler.codec.mqtt.MqttQoS; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.paho.mqttv5.client.IMqttToken; +import org.eclipse.paho.mqttv5.client.MqttDisconnectResponse; +import org.eclipse.paho.mqttv5.common.MqttException; +import org.eclipse.paho.mqttv5.common.MqttMessage; +import org.eclipse.paho.mqttv5.common.packet.MqttProperties; +import org.eclipse.paho.mqttv5.common.packet.MqttReturnCode; +import org.eclipse.paho.mqttv5.common.packet.MqttWireMessage; +import org.junit.Before; +import org.junit.Test; +import org.springframework.test.context.TestPropertySource; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.rpc.Rpc; +import org.thingsboard.server.common.data.rpc.RpcStatus; +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties; +import org.thingsboard.server.transport.mqtt.mqttv5.MqttV5TestCallback; +import org.thingsboard.server.transport.mqtt.mqttv5.MqttV5TestClient; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC; + +@Slf4j +@DaoSqlTest +@TestPropertySource(properties = { + "actors.rpc.close_session_on_rpc_delivery_timeout=true", + "transport.mqtt.timeout=100", +}) +public class MqttV5CloseTransportSessionOnRpcDeliveryTimeoutIntegrationTest extends AbstractMqttV5RpcTest { + + @Before + public void beforeTest() throws Exception { + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("RPC test device") + .build(); + processBeforeTest(configProperties); + } + + @Test + public void testOneWayRpcCloseSessionOnRpcDeliveryTimeout() throws Exception { + testCloseSessionOnRpcDeliveryTimeout("oneway"); + } + + @Test + public void testTwoWayRpcCloseSessionOnRpcDeliveryTimeout() throws Exception { + testCloseSessionOnRpcDeliveryTimeout("twoway"); + } + + private void testCloseSessionOnRpcDeliveryTimeout(String rpcType) throws Exception { + MqttV5TestClient client = new MqttV5TestClient(); + client.enableManualAcks(); + client.connectAndWait(accessToken); + MqttV5NoAckTestCallback callback = new MqttV5NoAckTestCallback(); + client.setCallback(callback); + client.subscribeAndWait(DEVICE_RPC_REQUESTS_SUB_TOPIC, MqttQoS.AT_LEAST_ONCE); + + String expectedReceivedPayload = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"23\",\"value\": 1}}"; + long expirationTime = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1); + String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"23\",\"value\": 1},\"persistent\":true,\"retries\":0,\"expirationTime\": " + expirationTime + "}"; + String result = doPostAsync("/api/rpc/" + rpcType + "/" + savedDevice.getId(), setGpioRequest, String.class, status().isOk()); + + assertThat(result).isNotNull(); + JsonNode response = JacksonUtil.toJsonNode(result); + assertThat(response).isNotNull(); + assertThat(response.hasNonNull("rpcId")).isTrue(); + + callback.getSubscribeLatch().await(DEFAULT_WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS); + assertThat(callback.getQoS()).isEqualTo(MqttQoS.AT_LEAST_ONCE.value()); + assertThat(JacksonUtil.fromBytes(callback.getPayloadBytes())) + .isEqualTo(JacksonUtil.toJsonNode(expectedReceivedPayload)); + + callback.getDisconnectLatch().await(DEFAULT_WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS); + assertThat(callback.getReturnCode()).isEqualTo(MqttReturnCode.RETURN_CODE_ADMINISTRITIVE_ACTION); + + Rpc persistedRpc = doGet("/api/rpc/persistent/" + response.get("rpcId").asText(), Rpc.class); + assertThat(persistedRpc).isNotNull(); + assertThat(persistedRpc.getStatus()).isEqualTo(RpcStatus.QUEUED); + assertThat(persistedRpc.getResponse()).isInstanceOf(NullNode.class); + assertThat(client.isConnected()).isFalse(); + } + + @Getter + private static class MqttV5NoAckTestCallback extends MqttV5TestCallback { + + private int returnCode; + + public CountDownLatch getDisconnectLatch() { + return super.getDeliveryLatch(); + } + + @Override + public void disconnected(MqttDisconnectResponse mqttDisconnectResponse) { + log.warn("MqttDisconnectResponse: {}", mqttDisconnectResponse); + returnCode = mqttDisconnectResponse.getReturnCode(); + getDisconnectLatch().countDown(); + } + + @Override + public void mqttErrorOccurred(MqttException e) { + log.warn("Error occurred:", e); + } + + @Override + public void messageArrived(String requestTopic, MqttMessage mqttMessage) { + log.warn("messageArrived on topic: {}", requestTopic); + qoS = mqttMessage.getQos(); + payloadBytes = mqttMessage.getPayload(); + subscribeLatch.countDown(); + } + + @Override + public void deliveryComplete(IMqttToken iMqttToken) { + // should be never called, Since we're never going to send a response back to server. + log.warn("delivery complete: {}", iMqttToken.getResponse()); + pubAckReceived = iMqttToken.getResponse().getType() == MqttWireMessage.MESSAGE_TYPE_PUBACK; + getDisconnectLatch().countDown(); + } + + @Override + public void connectComplete(boolean reconnect, String serverURI) { + log.warn("Connect completed: reconnect - {}, serverURI - {}", reconnect, serverURI); + } + + @Override + public void authPacketArrived(int reasonCode, MqttProperties mqttProperties) { + log.warn("Auth package received: reasonCode - {}, mqtt properties - {}", reasonCode, mqttProperties); + } + + + } + +} diff --git a/application/src/test/resources/logback-test.xml b/application/src/test/resources/logback-test.xml index f43144a24e..13c93da411 100644 --- a/application/src/test/resources/logback-test.xml +++ b/application/src/test/resources/logback-test.xml @@ -19,7 +19,7 @@ - + diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeEventService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeEventService.java index 2da05078ac..b7735228eb 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeEventService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeEventService.java @@ -28,9 +28,6 @@ public interface EdgeEventService { PageData findEdgeEvents(TenantId tenantId, EdgeId edgeId, Long seqIdStart, Long seqIdEnd, TimePageLink pageLink); - /** - * Executes stored procedure to cleanup old edge events. - * @param ttl the ttl for edge events in seconds - */ void cleanupEvents(long ttl); + } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java index c18f42ace7..d49a16e11f 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java @@ -32,7 +32,6 @@ import org.thingsboard.server.dao.entity.EntityDaoService; import java.util.List; import java.util.Optional; -import java.util.UUID; public interface EdgeService extends EntityDaoService { @@ -56,6 +55,8 @@ public interface EdgeService extends EntityDaoService { void deleteEdge(TenantId tenantId, EdgeId edgeId); + PageData findEdgeIdsByTenantId(TenantId tenantId, PageLink pageLink); + PageData findEdgesByTenantId(TenantId tenantId, PageLink pageLink); PageData findEdgesByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java index 586ae50f5d..6412921675 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java @@ -63,6 +63,8 @@ public interface UserService extends EntityDaoService { UserCredentials generateUserActivationToken(UserCredentials userCredentials); + UserCredentials checkUserActivationToken(TenantId tenantId, UserCredentials userCredentials); + UserCredentials replaceUserCredentials(TenantId tenantId, UserCredentials userCredentials); void deleteUser(TenantId tenantId, User user); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java index 209b0c46ee..56a5e135f8 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java @@ -143,5 +143,6 @@ public class DataConstants { public static final String QUEUE_NAME = "queueName"; public static final String EDGE_QUEUE_NAME = "Edge"; + public static final String EDGE_EVENT_QUEUE_NAME = "EdgeEvent"; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEvent.java b/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEvent.java index f8e59b308b..9c1449b749 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEvent.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEvent.java @@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.id.EdgeEventId; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; +import java.io.Serial; import java.util.UUID; @Data @@ -31,6 +32,7 @@ import java.util.UUID; @ToString(callSuper = true) public class EdgeEvent extends BaseData { + @Serial private static final long serialVersionUID = 5548866356798094088L; private long seqId; @@ -49,4 +51,5 @@ public class EdgeEvent extends BaseData { public EdgeEvent(EdgeEventId id) { super(id); } + } diff --git a/common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java b/common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java index 1d3b157ec2..fcef33b968 100644 --- a/common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java +++ b/common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java @@ -136,7 +136,7 @@ public class EdgeGrpcClient implements EdgeRpcClient { .setConnectRequestMsg(ConnectRequestMsg.newBuilder() .setEdgeRoutingKey(edgeKey) .setEdgeSecret(edgeSecret) - .setEdgeVersion(EdgeVersion.V_3_8_0) + .setEdgeVersion(EdgeVersion.V_3_9_0) .setMaxInboundMessageSize(maxInboundMessageSize) .build()) .build()); diff --git a/common/edge-api/src/main/proto/edge.proto b/common/edge-api/src/main/proto/edge.proto index 5fb51de2ab..11d320ffa7 100644 --- a/common/edge-api/src/main/proto/edge.proto +++ b/common/edge-api/src/main/proto/edge.proto @@ -40,6 +40,7 @@ enum EdgeVersion { V_3_6_4 = 6; V_3_7_0 = 7; V_3_8_0 = 8; + V_3_9_0 = 9; } /** diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/edge/EdgeEventUpdateMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/edge/EdgeEventUpdateMsg.java index 892b2a57dd..4c7ccb58ec 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/edge/EdgeEventUpdateMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/edge/EdgeEventUpdateMsg.java @@ -20,9 +20,12 @@ import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.MsgType; +import java.io.Serial; + @Data public class EdgeEventUpdateMsg implements EdgeSessionMsg { + @Serial private static final long serialVersionUID = -8050114506822836537L; private final TenantId tenantId; diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/edge/EdgeHighPriorityMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/edge/EdgeHighPriorityMsg.java index 03f039f2e4..325ae828e9 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/edge/EdgeHighPriorityMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/edge/EdgeHighPriorityMsg.java @@ -20,9 +20,14 @@ import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.MsgType; +import java.io.Serial; + @Data public class EdgeHighPriorityMsg implements EdgeSessionMsg { + @Serial + private static final long serialVersionUID = 2703437686242033551L; + private final TenantId tenantId; private final EdgeEvent edgeEvent; diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/edge/EdgeSessionMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/edge/EdgeSessionMsg.java index 4e658d6dd4..491deac52b 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/edge/EdgeSessionMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/edge/EdgeSessionMsg.java @@ -18,7 +18,9 @@ package org.thingsboard.server.common.msg.edge; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.MsgType; -public interface EdgeSessionMsg { +import java.io.Serializable; + +public interface EdgeSessionMsg extends Serializable { TenantId getTenantId(); diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/edge/FromEdgeSyncResponse.java b/common/message/src/main/java/org/thingsboard/server/common/msg/edge/FromEdgeSyncResponse.java index 1e8beea443..d6bb5c0e7f 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/edge/FromEdgeSyncResponse.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/edge/FromEdgeSyncResponse.java @@ -20,11 +20,13 @@ import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.MsgType; +import java.io.Serial; import java.util.UUID; @Data public class FromEdgeSyncResponse implements EdgeSessionMsg { + @Serial private static final long serialVersionUID = -6360890556315667486L; private final UUID id; diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/edge/ToEdgeSyncRequest.java b/common/message/src/main/java/org/thingsboard/server/common/msg/edge/ToEdgeSyncRequest.java index 04516b58c5..97ace1ccb1 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/edge/ToEdgeSyncRequest.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/edge/ToEdgeSyncRequest.java @@ -20,11 +20,13 @@ import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.MsgType; +import java.io.Serial; import java.util.UUID; @Data public class ToEdgeSyncRequest implements EdgeSessionMsg { + @Serial private static final long serialVersionUID = -7624597032448212259L; private final UUID id; diff --git a/common/proto/src/main/java/org/thingsboard/server/common/util/ProtoUtils.java b/common/proto/src/main/java/org/thingsboard/server/common/util/ProtoUtils.java index ab7a50705e..264b23f118 100644 --- a/common/proto/src/main/java/org/thingsboard/server/common/util/ProtoUtils.java +++ b/common/proto/src/main/java/org/thingsboard/server/common/util/ProtoUtils.java @@ -38,6 +38,7 @@ import org.thingsboard.server.common.data.device.data.CoapDeviceTransportConfigu import org.thingsboard.server.common.data.device.data.Lwm2mDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.PowerMode; import org.thingsboard.server.common.data.device.data.PowerSavingConfiguration; +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.ApiUsageStateId; @@ -181,6 +182,49 @@ public class ProtoUtils { ); } + public static TransportProtos.EdgeEventMsgProto toProto(EdgeEvent edgeEvent) { + TransportProtos.EdgeEventMsgProto.Builder builder = TransportProtos.EdgeEventMsgProto.newBuilder(); + + builder.setTenantIdMSB(edgeEvent.getTenantId().getId().getMostSignificantBits()); + builder.setTenantIdLSB(edgeEvent.getTenantId().getId().getLeastSignificantBits()); + builder.setEntityType(edgeEvent.getType().name()); + builder.setAction(edgeEvent.getAction().name()); + + if (edgeEvent.getEdgeId() != null) { + builder.setEdgeIdMSB(edgeEvent.getEdgeId().getId().getMostSignificantBits()); + builder.setEdgeIdLSB(edgeEvent.getEdgeId().getId().getLeastSignificantBits()); + } + if (edgeEvent.getEntityId() != null) { + builder.setEntityIdMSB(edgeEvent.getEntityId().getMostSignificantBits()); + builder.setEntityIdLSB(edgeEvent.getEntityId().getLeastSignificantBits()); + } + if (edgeEvent.getBody() != null) { + builder.setBody(JacksonUtil.toString(edgeEvent.getBody())); + } + + return builder.build(); + } + + public static EdgeEvent fromProto(TransportProtos.EdgeEventMsgProto proto) { + EdgeEvent edgeEvent = new EdgeEvent(); + TenantId tenantId = new TenantId(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())); + edgeEvent.setTenantId(tenantId); + edgeEvent.setType(EdgeEventType.valueOf(proto.getEntityType())); + edgeEvent.setAction(EdgeEventActionType.valueOf(proto.getAction())); + + if (proto.hasEdgeIdMSB() && proto.hasEdgeIdLSB()) { + edgeEvent.setEdgeId(new EdgeId(new UUID(proto.getEdgeIdMSB(), proto.getEdgeIdLSB()))); + } + if (proto.hasEntityIdMSB() && proto.hasEntityIdLSB()) { + edgeEvent.setEntityId(new UUID(proto.getEntityIdMSB(), proto.getEntityIdLSB())); + } + if (proto.hasBody()) { + edgeEvent.setBody(JacksonUtil.toJsonNode(proto.getBody())); + } + + return edgeEvent; + } + public static TransportProtos.EdgeHighPriorityMsgProto toProto(EdgeHighPriorityMsg msg) { TransportProtos.EdgeHighPriorityMsgProto.Builder builder = TransportProtos.EdgeHighPriorityMsgProto.newBuilder() .setTenantIdMSB(msg.getTenantId().getId().getMostSignificantBits()) diff --git a/common/proto/src/main/proto/queue.proto b/common/proto/src/main/proto/queue.proto index 3052b30b98..228a4039d2 100644 --- a/common/proto/src/main/proto/queue.proto +++ b/common/proto/src/main/proto/queue.proto @@ -608,6 +608,7 @@ enum SessionCloseReason { CREDENTIALS_UPDATED = 1; MAX_CONCURRENT_SESSIONS_LIMIT_REACHED = 2; SESSION_TIMEOUT = 3; + RPC_DELIVERY_TIMEOUT = 4; } message SessionCloseNotificationProto { @@ -1127,6 +1128,18 @@ message ComponentLifecycleMsgProto { ComponentLifecycleEvent event = 6; } +message EdgeEventMsgProto { + int64 tenantIdMSB = 1; + int64 tenantIdLSB = 2; + string entityType = 3; + string action = 4; + optional int64 edgeIdMSB = 5; + optional int64 edgeIdLSB = 6; + optional int64 entityIdMSB = 7; + optional int64 entityIdLSB = 8; + optional string body = 9; +} + message EdgeNotificationMsgProto { int64 tenantIdMSB = 1; int64 tenantIdLSB = 2; @@ -1522,7 +1535,6 @@ message ToCoreNotificationMsg { RestApiCallResponseMsgProto restApiCallResponseMsg = 50; } - /* Messages to Edge queue that are handled by ThingsBoard Core Service */ message ToEdgeMsg { EdgeNotificationMsgProto edgeNotificationMsg = 1; @@ -1536,6 +1548,10 @@ message ToEdgeNotificationMsg { ComponentLifecycleMsgProto componentLifecycle = 5; } +message ToEdgeEventNotificationMsg { + EdgeEventMsgProto edgeEventMsg = 1; +} + /* Messages that are handled by ThingsBoard RuleEngine Service */ message ToRuleEngineMsg { int64 tenantIdMSB = 1; diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/TopicService.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/TopicService.java index 8bd15a1ccf..927c311a2d 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/TopicService.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/TopicService.java @@ -17,6 +17,8 @@ package org.thingsboard.server.queue.discovery; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +import org.springframework.util.ConcurrentReferenceHashMap; +import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; @@ -33,6 +35,7 @@ public class TopicService { private final ConcurrentMap tbCoreNotificationTopics = new ConcurrentHashMap<>(); private final ConcurrentMap tbRuleEngineNotificationTopics = new ConcurrentHashMap<>(); private final ConcurrentMap tbEdgeNotificationTopics = new ConcurrentHashMap<>(); + private final ConcurrentReferenceHashMap tbEdgeEventsNotificationTopics = new ConcurrentReferenceHashMap<>(); /** * Each Service should start a consumer for messages that target individual service instance based on serviceId. @@ -59,6 +62,14 @@ public class TopicService { return buildTopicPartitionInfo("tb_edge.notifications." + serviceId, null, null, false); } + public TopicPartitionInfo getEdgeEventNotificationsTopic(TenantId tenantId, EdgeId edgeId) { + return tbEdgeEventsNotificationTopics.computeIfAbsent(edgeId, id -> buildEdgeEventNotificationsTopicPartitionInfo(tenantId, edgeId)); + } + + public TopicPartitionInfo buildEdgeEventNotificationsTopicPartitionInfo(TenantId tenantId, EdgeId edgeId) { + return buildTopicPartitionInfo("tb_edge_event.notifications." + tenantId + "." + edgeId, null, null, false); + } + private TopicPartitionInfo buildNotificationsTopicPartitionInfo(ServiceType serviceType, String serviceId) { return buildTopicPartitionInfo(serviceType.name().toLowerCase() + ".notifications." + serviceId, null, null, false); } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaAdmin.java b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaAdmin.java index 2bd283f1e5..2ea11c7afa 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaAdmin.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaAdmin.java @@ -17,7 +17,10 @@ package org.thingsboard.server.queue.kafka; import lombok.extern.slf4j.Slf4j; import org.apache.kafka.clients.admin.CreateTopicsResult; +import org.apache.kafka.clients.admin.ListOffsetsResult; import org.apache.kafka.clients.admin.NewTopic; +import org.apache.kafka.clients.admin.OffsetSpec; +import org.apache.kafka.clients.admin.TopicDescription; import org.apache.kafka.clients.consumer.OffsetAndMetadata; import org.apache.kafka.common.TopicPartition; import org.apache.kafka.common.errors.TopicExistsException; @@ -25,12 +28,14 @@ import org.thingsboard.server.queue.TbQueueAdmin; import org.thingsboard.server.queue.util.PropertyUtils; import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.stream.Collectors; /** * Created by ashvayka on 24.09.18. @@ -116,6 +121,15 @@ public class TbKafkaAdmin implements TbQueueAdmin { return topics; } + public Set getAllTopics() { + try { + return settings.getAdminClient().listTopics().names().get(); + } catch (InterruptedException | ExecutionException e) { + log.error("Failed to get all topics.", e); + } + return null; + } + public CreateTopicsResult createTopic(NewTopic topic) { return settings.getAdminClient().createTopics(Collections.singletonList(topic)); } @@ -170,7 +184,33 @@ public class TbKafkaAdmin implements TbQueueAdmin { log.info("[{}] altered new consumer groupId {}", tp, newGroupId); break; } + } + + public boolean isTopicEmpty(String topic) { + try { + TopicDescription topicDescription = settings.getAdminClient().describeTopics(Collections.singletonList(topic)).topicNameValues().get(topic).get(); + List partitions = topicDescription.partitions().stream().map(partitionInfo -> new TopicPartition(topic, partitionInfo.partition())).toList(); + Map beginningOffsets = settings.getAdminClient().listOffsets(partitions.stream() + .collect(Collectors.toMap(partition -> partition, partition -> OffsetSpec.earliest()))).all().get(); + + Map endOffsets = settings.getAdminClient().listOffsets(partitions.stream() + .collect(Collectors.toMap(partition -> partition, partition -> OffsetSpec.latest()))).all().get(); + + for (TopicPartition partition : partitions) { + long beginningOffset = beginningOffsets.get(partition).offset(); + long endOffset = endOffsets.get(partition).offset(); + + if (beginningOffset != endOffset) { + log.debug("Partition [{}] of topic [{}] is not empty. Returning false.", partition.partition(), topic); + return false; + } + } + return true; + } catch (InterruptedException | ExecutionException e) { + log.error("Failed to check if topic [{}] is empty.", topic, e); + return false; + } } } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaSettings.java b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaSettings.java index 11b7df1958..f228bbae9a 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaSettings.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaSettings.java @@ -50,6 +50,8 @@ import java.util.Properties; @Component public class TbKafkaSettings { + private static final List DYNAMIC_TOPICS = List.of("tb_edge_event.notifications"); + @Value("${queue.kafka.bootstrap.servers}") private String servers; @@ -164,6 +166,15 @@ public class TbKafkaSettings { consumerPropertiesPerTopic .getOrDefault(topic, Collections.emptyList()) .forEach(kv -> props.put(kv.getKey(), kv.getValue())); + + if (topic != null) { + DYNAMIC_TOPICS.stream() + .filter(topic::startsWith) + .findFirst() + .ifPresent(prefix -> consumerPropertiesPerTopic.getOrDefault(prefix, Collections.emptyList()) + .forEach(kv -> props.put(kv.getKey(), kv.getValue()))); + } + return props; } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaTopicConfigs.java b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaTopicConfigs.java index 300548e081..ee529e8a68 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaTopicConfigs.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaTopicConfigs.java @@ -46,6 +46,8 @@ public class TbKafkaTopicConfigs { private String vcProperties; @Value("${queue.kafka.topic-properties.edge:}") private String edgeProperties; + @Value("${queue.kafka.topic-properties.edge-event:}") + private String edgeEventProperties; @Value("${queue.kafka.topic-properties.housekeeper:}") private String housekeeperProperties; @Value("${queue.kafka.topic-properties.housekeeper-reprocessing:}") @@ -75,6 +77,8 @@ public class TbKafkaTopicConfigs { private Map housekeeperReprocessingConfigs; @Getter private Map edgeConfigs; + @Getter + private Map edgeEventConfigs; @PostConstruct private void init() { @@ -92,6 +96,7 @@ public class TbKafkaTopicConfigs { housekeeperConfigs = PropertyUtils.getProps(housekeeperProperties); housekeeperReprocessingConfigs = PropertyUtils.getProps(housekeeperReprocessingProperties); edgeConfigs = PropertyUtils.getProps(edgeProperties); + edgeEventConfigs = PropertyUtils.getProps(edgeEventProperties); } } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsMonolithQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsMonolithQueueFactory.java index 005e9aee17..310cc1d1c5 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsMonolithQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsMonolithQueueFactory.java @@ -26,6 +26,7 @@ import org.thingsboard.server.gen.js.JsInvokeProtos.RemoteJsRequest; import org.thingsboard.server.gen.js.JsInvokeProtos.RemoteJsResponse; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeEventNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToHousekeeperServiceMsg; @@ -281,6 +282,11 @@ public class AwsSqsMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEng topicService.getEdgeNotificationsTopic(serviceInfoProvider.getServiceId()).getFullTopicName()); } + @Override + public TbQueueProducer> createEdgeEventMsgProducer() { + return null; + } + @PreDestroy private void destroy() { if (coreAdmin != null) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbRuleEngineQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbRuleEngineQueueFactory.java index 37319e378b..a93aba2764 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbRuleEngineQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbRuleEngineQueueFactory.java @@ -23,10 +23,10 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.queue.Queue; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.js.JsInvokeProtos; -import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToHousekeeperServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; @@ -131,7 +131,7 @@ public class AwsSqsTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory } @Override - public TbQueueProducer> createEdgeNotificationsMsgProducer() { + public TbQueueProducer> createEdgeNotificationsMsgProducer() { return new TbAwsSqsProducerTemplate<>(notificationAdmin, sqsSettings, topicService.getEdgeNotificationsTopic(serviceInfoProvider.getServiceId()).getFullTopicName()); } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryMonolithQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryMonolithQueueFactory.java index c4281bc390..d70cad159b 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryMonolithQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryMonolithQueueFactory.java @@ -204,6 +204,11 @@ public class InMemoryMonolithQueueFactory implements TbCoreQueueFactory, TbRuleE return new InMemoryTbQueueProducer<>(storage, topicService.getEdgeNotificationsTopic(serviceInfoProvider.getServiceId()).getFullTopicName()); } + @Override + public TbQueueProducer> createEdgeEventMsgProducer() { + return null; + } + @Scheduled(fixedRateString = "${queue.in_memory.stats.print-interval-ms:60000}") private void printInMemoryStats() { storage.printStats(); diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java index 46d94f094f..dd5d61e834 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java @@ -20,11 +20,14 @@ import jakarta.annotation.PreDestroy; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.id.EdgeId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.queue.Queue; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.js.JsInvokeProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeEventNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToHousekeeperServiceMsg; @@ -90,6 +93,7 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi private final TbQueueAdmin housekeeperAdmin; private final TbQueueAdmin housekeeperReprocessingAdmin; private final TbQueueAdmin edgeAdmin; + private final TbQueueAdmin edgeEventAdmin; private final AtomicLong consumerCount = new AtomicLong(); @@ -128,6 +132,7 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi this.housekeeperAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getHousekeeperConfigs()); this.housekeeperReprocessingAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getHousekeeperReprocessingConfigs()); this.edgeAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getEdgeConfigs()); + this.edgeEventAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getEdgeEventConfigs()); } @Override @@ -462,6 +467,29 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi return requestBuilder.build(); } + @Override + public TbQueueConsumer> createEdgeEventMsgConsumer(TenantId tenantId, EdgeId edgeId) { + TbKafkaConsumerTemplate.TbKafkaConsumerTemplateBuilder> consumerBuilder = TbKafkaConsumerTemplate.builder(); + consumerBuilder.settings(kafkaSettings); + consumerBuilder.topic(topicService.buildTopicName("tb_edge_event.notifications." + tenantId + "." + edgeId)); + consumerBuilder.clientId("monolith-to-edge-event-consumer" + serviceInfoProvider.getServiceId()); + consumerBuilder.groupId(topicService.buildTopicName("monolith-edge-event-consumer")); + consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), ToEdgeEventNotificationMsg.parseFrom(msg.getData()), msg.getHeaders())); + consumerBuilder.admin(edgeEventAdmin); + consumerBuilder.statsService(consumerStatsService); + return consumerBuilder.build(); + } + + @Override + public TbQueueProducer> createEdgeEventMsgProducer() { + TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder> requestBuilder = TbKafkaProducerTemplate.builder(); + requestBuilder.settings(kafkaSettings); + requestBuilder.clientId("monolith-to-edge-event-" + serviceInfoProvider.getServiceId()); + requestBuilder.defaultTopic(topicService.buildTopicName("edge-events")); + requestBuilder.admin(edgeEventAdmin); + return requestBuilder.build(); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbCoreQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbCoreQueueFactory.java index b8b51706b2..cc0e044917 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbCoreQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbCoreQueueFactory.java @@ -20,10 +20,13 @@ import jakarta.annotation.PreDestroy; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.id.EdgeId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.js.JsInvokeProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeEventNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToHousekeeperServiceMsg; @@ -89,6 +92,7 @@ public class KafkaTbCoreQueueFactory implements TbCoreQueueFactory { private final TbQueueAdmin housekeeperAdmin; private final TbQueueAdmin housekeeperReprocessingAdmin; private final TbQueueAdmin edgeAdmin; + private final TbQueueAdmin edgeEventAdmin; private final AtomicLong consumerCount = new AtomicLong(); @@ -128,6 +132,7 @@ public class KafkaTbCoreQueueFactory implements TbCoreQueueFactory { this.housekeeperAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getHousekeeperConfigs()); this.housekeeperReprocessingAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getHousekeeperReprocessingConfigs()); this.edgeAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getEdgeConfigs()); + this.edgeEventAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getEdgeEventConfigs()); } @Override @@ -411,6 +416,29 @@ public class KafkaTbCoreQueueFactory implements TbCoreQueueFactory { return requestBuilder.build(); } + @Override + public TbQueueConsumer> createEdgeEventMsgConsumer(TenantId tenantId, EdgeId edgeId) { + TbKafkaConsumerTemplate.TbKafkaConsumerTemplateBuilder> consumerBuilder = TbKafkaConsumerTemplate.builder(); + consumerBuilder.settings(kafkaSettings); + consumerBuilder.topic(topicService.buildTopicName("tb_edge_event.notifications." + tenantId + "." + edgeId)); + consumerBuilder.clientId("tb-core-edge-event-consumer" + serviceInfoProvider.getServiceId()); + consumerBuilder.groupId(topicService.buildTopicName("tb-core-edge-event-consumer")); + consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), ToEdgeEventNotificationMsg.parseFrom(msg.getData()), msg.getHeaders())); + consumerBuilder.admin(edgeEventAdmin); + consumerBuilder.statsService(consumerStatsService); + return consumerBuilder.build(); + } + + @Override + public TbQueueProducer> createEdgeEventMsgProducer() { + TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder> requestBuilder = TbKafkaProducerTemplate.builder(); + requestBuilder.settings(kafkaSettings); + requestBuilder.clientId("tb-core-edge-event-" + serviceInfoProvider.getServiceId()); + requestBuilder.defaultTopic(topicService.buildTopicName("edge-events")); + requestBuilder.admin(edgeEventAdmin); + return requestBuilder.build(); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbRuleEngineQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbRuleEngineQueueFactory.java index 485830caf4..87a1a69c2e 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbRuleEngineQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbRuleEngineQueueFactory.java @@ -23,10 +23,11 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.queue.Queue; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.js.JsInvokeProtos; -import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeEventNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToHousekeeperServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; @@ -79,6 +80,7 @@ public class KafkaTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory { private final TbQueueAdmin fwUpdatesAdmin; private final TbQueueAdmin housekeeperAdmin; private final TbQueueAdmin edgeAdmin; + private final TbQueueAdmin edgeEventAdmin; private final AtomicLong consumerCount = new AtomicLong(); public KafkaTbRuleEngineQueueFactory(TopicService topicService, TbKafkaSettings kafkaSettings, @@ -108,6 +110,7 @@ public class KafkaTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory { this.fwUpdatesAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getFwUpdatesConfigs()); this.housekeeperAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getHousekeeperConfigs()); this.edgeAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getEdgeConfigs()); + this.edgeEventAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getEdgeEventConfigs()); } @Override @@ -181,8 +184,8 @@ public class KafkaTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory { } @Override - public TbQueueProducer> createEdgeNotificationsMsgProducer() { - TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder> requestBuilder = TbKafkaProducerTemplate.builder(); + public TbQueueProducer> createEdgeNotificationsMsgProducer() { + TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder> requestBuilder = TbKafkaProducerTemplate.builder(); requestBuilder.settings(kafkaSettings); requestBuilder.clientId("tb-rule-engine-to-edge-notifications-" + serviceInfoProvider.getServiceId()); requestBuilder.defaultTopic(topicService.getEdgeNotificationsTopic(serviceInfoProvider.getServiceId()).getFullTopicName()); @@ -190,6 +193,15 @@ public class KafkaTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory { return requestBuilder.build(); } + @Override + public TbQueueProducer> createEdgeEventMsgProducer() { + TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder> requestBuilder = TbKafkaProducerTemplate.builder(); + requestBuilder.settings(kafkaSettings); + requestBuilder.clientId("tb-rule-engine-edge-event-" + serviceInfoProvider.getServiceId()); + requestBuilder.admin(edgeEventAdmin); + return requestBuilder.build(); + } + @Override public TbQueueConsumer> createToRuleEngineMsgConsumer(Queue configuration) { throw new UnsupportedOperationException("Rule engine consumer should use a partitionId"); diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubMonolithQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubMonolithQueueFactory.java index 3df48e8fb8..05bef819b7 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubMonolithQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubMonolithQueueFactory.java @@ -26,6 +26,7 @@ import org.thingsboard.server.gen.js.JsInvokeProtos.RemoteJsRequest; import org.thingsboard.server.gen.js.JsInvokeProtos.RemoteJsResponse; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeEventNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToHousekeeperServiceMsg; @@ -282,6 +283,11 @@ public class PubSubMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEng topicService.getEdgeNotificationsTopic(serviceInfoProvider.getServiceId()).getFullTopicName()); } + @Override + public TbQueueProducer> createEdgeEventMsgProducer() { + return null; + } + @PreDestroy private void destroy() { if (coreAdmin != null) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbRuleEngineQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbRuleEngineQueueFactory.java index bb46610de5..671da15f20 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbRuleEngineQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbRuleEngineQueueFactory.java @@ -23,10 +23,11 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.queue.Queue; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.js.JsInvokeProtos; -import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeEventNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToHousekeeperServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; @@ -130,10 +131,15 @@ public class PubSubTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory } @Override - public TbQueueProducer> createEdgeNotificationsMsgProducer() { + public TbQueueProducer> createEdgeNotificationsMsgProducer() { return new TbPubSubProducerTemplate<>(notificationAdmin, pubSubSettings, topicService.getEdgeNotificationsTopic(serviceInfoProvider.getServiceId()).getFullTopicName()); } + @Override + public TbQueueProducer> createEdgeEventMsgProducer() { + return null; + } + @Override public TbQueueConsumer> createToRuleEngineMsgConsumer(Queue configuration) { return new TbPubSubConsumerTemplate<>(ruleEngineAdmin, pubSubSettings, topicService.buildTopicName(configuration.getTopic()), diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqMonolithQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqMonolithQueueFactory.java index 8ad400d9e3..3c59923def 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqMonolithQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqMonolithQueueFactory.java @@ -26,6 +26,7 @@ import org.thingsboard.server.gen.js.JsInvokeProtos.RemoteJsRequest; import org.thingsboard.server.gen.js.JsInvokeProtos.RemoteJsResponse; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeEventNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToHousekeeperServiceMsg; @@ -280,6 +281,11 @@ public class RabbitMqMonolithQueueFactory implements TbCoreQueueFactory, TbRuleE topicService.getEdgeNotificationsTopic(serviceInfoProvider.getServiceId()).getFullTopicName()); } + @Override + public TbQueueProducer> createEdgeEventMsgProducer() { + return null; + } + @PreDestroy private void destroy() { if (coreAdmin != null) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbRuleEngineQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbRuleEngineQueueFactory.java index 3dabaf100b..a7af729e1f 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbRuleEngineQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbRuleEngineQueueFactory.java @@ -23,10 +23,10 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.queue.Queue; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.js.JsInvokeProtos; -import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToHousekeeperServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; @@ -129,7 +129,7 @@ public class RabbitMqTbRuleEngineQueueFactory implements TbRuleEngineQueueFactor } @Override - public TbQueueProducer> createEdgeNotificationsMsgProducer() { + public TbQueueProducer> createEdgeNotificationsMsgProducer() { return new TbRabbitMqProducerTemplate<>(notificationAdmin, rabbitMqSettings, topicService.getEdgeNotificationsTopic(serviceInfoProvider.getServiceId()).getFullTopicName()); } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusMonolithQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusMonolithQueueFactory.java index fa7e73b879..932bbd21e5 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusMonolithQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusMonolithQueueFactory.java @@ -25,6 +25,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.js.JsInvokeProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeEventNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToHousekeeperServiceMsg; @@ -279,6 +280,11 @@ public class ServiceBusMonolithQueueFactory implements TbCoreQueueFactory, TbRul topicService.getEdgeNotificationsTopic(serviceInfoProvider.getServiceId()).getFullTopicName()); } + @Override + public TbQueueProducer> createEdgeEventMsgProducer() { + return null; + } + @PreDestroy private void destroy() { if (coreAdmin != null) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbRuleEngineQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbRuleEngineQueueFactory.java index 643a9dee3b..54661f695c 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbRuleEngineQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbRuleEngineQueueFactory.java @@ -23,10 +23,10 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.queue.Queue; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.js.JsInvokeProtos; -import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToHousekeeperServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; @@ -129,7 +129,7 @@ public class ServiceBusTbRuleEngineQueueFactory implements TbRuleEngineQueueFact } @Override - public TbQueueProducer> createEdgeNotificationsMsgProducer() { + public TbQueueProducer> createEdgeNotificationsMsgProducer() { return new TbServiceBusProducerTemplate<>(notificationAdmin, serviceBusSettings, topicService.getEdgeNotificationsTopic(serviceInfoProvider.getServiceId()).getFullTopicName()); } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueFactory.java index cecfddf7a7..c4002f4d3e 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueFactory.java @@ -15,9 +15,12 @@ */ package org.thingsboard.server.queue.provider; +import org.thingsboard.server.common.data.id.EdgeId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.gen.js.JsInvokeProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeEventNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToHousekeeperServiceMsg; @@ -148,4 +151,12 @@ public interface TbCoreQueueFactory extends TbUsageStatsClientQueueFactory, Hous TbQueueProducer> createEdgeNotificationsMsgProducer(); + default TbQueueConsumer> createEdgeEventMsgConsumer(TenantId tenantId, EdgeId edgeId) { + return null; + } + + default TbQueueProducer> createEdgeEventMsgProducer() { + return null; + } + } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueProducerProvider.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueProducerProvider.java index cf94bf68d0..9cf18e6cb4 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueProducerProvider.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueProducerProvider.java @@ -19,6 +19,7 @@ import jakarta.annotation.PostConstruct; import org.springframework.stereotype.Service; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeEventNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToHousekeeperServiceMsg; @@ -43,6 +44,7 @@ public class TbCoreQueueProducerProvider implements TbQueueProducerProvider { private TbQueueProducer> toTbCoreNotifications; private TbQueueProducer> toEdge; private TbQueueProducer> toEdgeNotifications; + private TbQueueProducer> toEdgeEvents; private TbQueueProducer> toUsageStats; private TbQueueProducer> toVersionControl; private TbQueueProducer> toHousekeeper; @@ -63,6 +65,7 @@ public class TbCoreQueueProducerProvider implements TbQueueProducerProvider { this.toHousekeeper = tbQueueProvider.createHousekeeperMsgProducer(); this.toEdge = tbQueueProvider.createEdgeMsgProducer(); this.toEdgeNotifications = tbQueueProvider.createEdgeNotificationsMsgProducer(); + this.toEdgeEvents = tbQueueProvider.createEdgeEventMsgProducer(); } @Override @@ -116,4 +119,9 @@ public class TbCoreQueueProducerProvider implements TbQueueProducerProvider { return toEdgeNotifications; } + @Override + public TbQueueProducer> getTbEdgeEventsMsgProducer() { + return toEdgeEvents; + } + } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbQueueProducerProvider.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbQueueProducerProvider.java index a0c43a9e4b..ec31763baa 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbQueueProducerProvider.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbQueueProducerProvider.java @@ -17,6 +17,7 @@ package org.thingsboard.server.queue.provider; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeEventNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToHousekeeperServiceMsg; @@ -88,4 +89,6 @@ public interface TbQueueProducerProvider { TbQueueProducer> getTbEdgeNotificationsMsgProducer(); + TbQueueProducer> getTbEdgeEventsMsgProducer(); + } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineProducerProvider.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineProducerProvider.java index 8797064204..e9f7773a26 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineProducerProvider.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineProducerProvider.java @@ -20,6 +20,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeEventNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToHousekeeperServiceMsg; @@ -45,6 +46,7 @@ public class TbRuleEngineProducerProvider implements TbQueueProducerProvider { private TbQueueProducer> toHousekeeper; private TbQueueProducer> toEdge; private TbQueueProducer> toEdgeNotifications; + private TbQueueProducer> toEdgeEvents; public TbRuleEngineProducerProvider(TbRuleEngineQueueFactory tbQueueProvider) { this.tbQueueProvider = tbQueueProvider; @@ -61,6 +63,7 @@ public class TbRuleEngineProducerProvider implements TbQueueProducerProvider { this.toHousekeeper = tbQueueProvider.createHousekeeperMsgProducer(); this.toEdge = tbQueueProvider.createEdgeMsgProducer(); this.toEdgeNotifications = tbQueueProvider.createEdgeNotificationsMsgProducer(); + this.toEdgeEvents = tbQueueProvider.createEdgeEventMsgProducer(); } @Override @@ -98,6 +101,11 @@ public class TbRuleEngineProducerProvider implements TbQueueProducerProvider { return toEdgeNotifications; } + @Override + public TbQueueProducer> getTbEdgeEventsMsgProducer() { + return toEdgeEvents; + } + @Override public TbQueueProducer> getTbUsageStatsMsgProducer() { return toUsageStats; diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineQueueFactory.java index dda3f33ed4..c406aeb311 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineQueueFactory.java @@ -17,10 +17,11 @@ package org.thingsboard.server.queue.provider; import org.thingsboard.server.common.data.queue.Queue; import org.thingsboard.server.gen.js.JsInvokeProtos; -import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeEventNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; @@ -74,7 +75,11 @@ public interface TbRuleEngineQueueFactory extends TbUsageStatsClientQueueFactory TbQueueProducer> createEdgeMsgProducer(); - TbQueueProducer> createEdgeNotificationsMsgProducer(); + TbQueueProducer> createEdgeNotificationsMsgProducer(); + + default TbQueueProducer> createEdgeEventMsgProducer() { + return null; + } /** * Used to consume messages about firmware update notifications to TB Core Service diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbTransportQueueProducerProvider.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbTransportQueueProducerProvider.java index b2c7856b20..7960cbcf32 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbTransportQueueProducerProvider.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbTransportQueueProducerProvider.java @@ -20,6 +20,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeEventNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToHousekeeperServiceMsg; @@ -82,7 +83,7 @@ public class TbTransportQueueProducerProvider implements TbQueueProducerProvider @Override public TbQueueProducer> getTbEdgeMsgProducer() { - throw new RuntimeException("Not Implemented! Should not be used Transport!"); + throw new RuntimeException("Not Implemented! Should not be used by Transport!"); } @Override @@ -90,6 +91,11 @@ public class TbTransportQueueProducerProvider implements TbQueueProducerProvider throw new RuntimeException("Not Implemented! Should not be used by Transport!"); } + @Override + public TbQueueProducer> getTbEdgeEventsMsgProducer() { + throw new RuntimeException("Not Implemented! Should not be used by Transport!"); + } + @Override public TbQueueProducer> getTbVersionControlMsgProducer() { throw new RuntimeException("Not Implemented! Should not be used by Transport!"); diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbVersionControlProducerProvider.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbVersionControlProducerProvider.java index a8f3b124e2..cd4fa12df0 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbVersionControlProducerProvider.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbVersionControlProducerProvider.java @@ -20,6 +20,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeEventNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToHousekeeperServiceMsg; @@ -86,6 +87,11 @@ public class TbVersionControlProducerProvider implements TbQueueProducerProvider throw new RuntimeException("Not Implemented! Should not be used by Version Control Service!"); } + @Override + public TbQueueProducer> getTbEdgeEventsMsgProducer() { + throw new RuntimeException("Not Implemented! Should not be used by Version Control Service!"); + } + @Override public TbQueueProducer> getTbVersionControlMsgProducer() { throw new RuntimeException("Not Implemented! Should not be used by Version Control Service!"); diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/DefaultTbelInvokeService.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/DefaultTbelInvokeService.java index df6f5f58c8..74d6a4e6f5 100644 --- a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/DefaultTbelInvokeService.java +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/DefaultTbelInvokeService.java @@ -251,4 +251,8 @@ public class DefaultTbelInvokeService extends AbstractScriptInvokeService implem return hasher.hash().toString(); } + @Override + protected long getMaxEvalRequestsTimeout() { + return maxInvokeRequestsTimeout * 2; + } } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index d7f64e58ee..2b7e2a51dc 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -313,6 +313,14 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement sendResponseForAdaptorErrorOrCloseContext(ctx, topicName, msgId); } break; + case SUBSCRIBE: + MqttSubscribeMessage subscribeMessage = (MqttSubscribeMessage) msg; + processSubscribe(ctx, subscribeMessage); + break; + case UNSUBSCRIBE: + MqttUnsubscribeMessage unsubscribeMessage = (MqttUnsubscribeMessage) msg; + processUnsubscribe(ctx, unsubscribeMessage); + break; case PINGREQ: ctx.writeAndFlush(new MqttMessage(new MqttFixedHeader(PINGRESP, false, AT_MOST_ONCE, false, 0))); break; @@ -750,7 +758,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } private void processSubscribe(ChannelHandlerContext ctx, MqttSubscribeMessage mqttMsg) { - if (!checkConnected(ctx, mqttMsg)) { + if (!checkConnected(ctx, mqttMsg) && !deviceSessionCtx.isProvisionOnly()) { ctx.writeAndFlush(createSubAckMessage(mqttMsg.variableHeader().messageId(), Collections.singletonList(MqttReasonCodes.SubAck.NOT_AUTHORIZED.byteValue() & 0xFF))); return; } @@ -760,6 +768,16 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement for (MqttTopicSubscription subscription : mqttMsg.payload().topicSubscriptions()) { String topic = subscription.topicName(); MqttQoS reqQoS = subscription.qualityOfService(); + if (deviceSessionCtx.isProvisionOnly()) { + if (MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC.equals(topic)) { + registerSubQoS(topic, grantedQoSList, reqQoS); + } else { + log.debug("[{}] Failed to subscribe to [{}][{}]", sessionId, topic, reqQoS); + grantedQoSList.add(ReturnCodeResolver.getSubscriptionReturnCode(deviceSessionCtx.getMqttVersion(), MqttReasonCodes.SubAck.TOPIC_FILTER_INVALID)); + } + activityReported = true; + continue; + } if (deviceSessionCtx.isDeviceSubscriptionAttributesTopic(topic)) { processAttributesSubscribe(grantedQoSList, topic, reqQoS, TopicType.V1); activityReported = true; @@ -822,7 +840,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement case MqttTopics.GATEWAY_ATTRIBUTES_TOPIC: case MqttTopics.GATEWAY_RPC_TOPIC: case MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC: - case MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC: case MqttTopics.DEVICE_FIRMWARE_RESPONSES_TOPIC: case MqttTopics.DEVICE_FIRMWARE_ERROR_TOPIC: case MqttTopics.DEVICE_SOFTWARE_RESPONSES_TOPIC: @@ -873,7 +890,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } private void processUnsubscribe(ChannelHandlerContext ctx, MqttUnsubscribeMessage mqttMsg) { - if (!checkConnected(ctx, mqttMsg)) { + if (!checkConnected(ctx, mqttMsg) && !deviceSessionCtx.isProvisionOnly()) { ctx.writeAndFlush(createUnSubAckMessage(mqttMsg.variableHeader().messageId(), Collections.singletonList((short) MqttReasonCodes.UnsubAck.NOT_AUTHORIZED.byteValue()))); return; @@ -887,6 +904,14 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement mqttQoSMap.remove(matcher); try { short resultValue = MqttReasonCodes.UnsubAck.SUCCESS.byteValue(); + if (deviceSessionCtx.isProvisionOnly()) { + if (!matcher.matches(MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC)) { + resultValue = MqttReasonCodes.UnsubAck.TOPIC_FILTER_INVALID.byteValue(); + } + unSubResults.add(resultValue); + activityReported = true; + continue; + } switch (topicName) { case MqttTopics.DEVICE_ATTRIBUTES_TOPIC: case MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC: @@ -917,7 +942,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement case MqttTopics.GATEWAY_ATTRIBUTES_TOPIC: case MqttTopics.GATEWAY_RPC_TOPIC: case MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC: - case MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC: case MqttTopics.DEVICE_FIRMWARE_RESPONSES_TOPIC: case MqttTopics.DEVICE_FIRMWARE_ERROR_TOPIC: case MqttTopics.DEVICE_SOFTWARE_RESPONSES_TOPIC: @@ -1304,18 +1328,12 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement public void onRemoteSessionCloseCommand(UUID sessionId, TransportProtos.SessionCloseNotificationProto sessionCloseNotification) { log.trace("[{}] Received the remote command to close the session: {}", sessionId, sessionCloseNotification.getMessage()); transportService.deregisterSession(deviceSessionCtx.getSessionInfo()); - MqttReasonCodes.Disconnect returnCode = MqttReasonCodes.Disconnect.IMPLEMENTATION_SPECIFIC_ERROR; - switch (sessionCloseNotification.getReason()) { - case CREDENTIALS_UPDATED: - returnCode = MqttReasonCodes.Disconnect.ADMINISTRATIVE_ACTION; - break; - case MAX_CONCURRENT_SESSIONS_LIMIT_REACHED: - returnCode = MqttReasonCodes.Disconnect.SESSION_TAKEN_OVER; - break; - case SESSION_TIMEOUT: - returnCode = MqttReasonCodes.Disconnect.MAXIMUM_CONNECT_TIME; - break; - } + MqttReasonCodes.Disconnect returnCode = switch (sessionCloseNotification.getReason()) { + case CREDENTIALS_UPDATED, RPC_DELIVERY_TIMEOUT -> MqttReasonCodes.Disconnect.ADMINISTRATIVE_ACTION; + case MAX_CONCURRENT_SESSIONS_LIMIT_REACHED -> MqttReasonCodes.Disconnect.SESSION_TAKEN_OVER; + case SESSION_TIMEOUT -> MqttReasonCodes.Disconnect.MAXIMUM_CONNECT_TIME; + default -> MqttReasonCodes.Disconnect.IMPLEMENTATION_SPECIFIC_ERROR; + }; closeCtx(deviceSessionCtx.getChannel(), returnCode); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/edge/BaseEdgeEventService.java b/dao/src/main/java/org/thingsboard/server/dao/edge/BaseEdgeEventService.java index 274b0154a5..b8736e913a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/edge/BaseEdgeEventService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/edge/BaseEdgeEventService.java @@ -15,17 +15,7 @@ */ package org.thingsboard.server.dao.edge; -import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import jakarta.annotation.PostConstruct; -import jakarta.annotation.PreDestroy; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.NotNull; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.stereotype.Service; -import org.thingsboard.common.util.ThingsBoardThreadFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.cache.limits.RateLimitService; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.edge.EdgeEvent; @@ -35,39 +25,28 @@ import org.thingsboard.server.common.data.limit.LimitedApi; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.msg.tools.TbRateLimitsException; -import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; import org.thingsboard.server.dao.service.DataValidator; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; +public abstract class BaseEdgeEventService implements EdgeEventService { -@Service -@Slf4j -@RequiredArgsConstructor -public class BaseEdgeEventService implements EdgeEventService { + @Autowired + private EdgeEventDao edgeEventDao; + @Autowired + private RateLimitService rateLimitService; + @Autowired + private DataValidator edgeEventValidator; - private final EdgeEventDao edgeEventDao; - private final RateLimitService rateLimitService; - private final DataValidator edgeEventValidator; - - private final ApplicationEventPublisher eventPublisher; - - private ExecutorService edgeEventExecutor; - - @PostConstruct - public void initExecutor() { - edgeEventExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("edge-event-service")); + @Override + public PageData findEdgeEvents(TenantId tenantId, EdgeId edgeId, Long seqIdStart, Long seqIdEnd, TimePageLink pageLink) { + return edgeEventDao.findEdgeEvents(tenantId.getId(), edgeId, seqIdStart, seqIdEnd, pageLink); } - @PreDestroy - public void shutdownExecutor() { - if (edgeEventExecutor != null) { - edgeEventExecutor.shutdown(); - } + @Override + public void cleanupEvents(long ttl) { + edgeEventDao.cleanupEvents(ttl); } - @Override - public ListenableFuture saveAsync(EdgeEvent edgeEvent) { + protected void validateEdgeEvent(EdgeEvent edgeEvent) { if (!rateLimitService.checkRateLimit(LimitedApi.EDGE_EVENTS, edgeEvent.getTenantId())) { throw new TbRateLimitsException(EntityType.TENANT); } @@ -75,30 +54,6 @@ public class BaseEdgeEventService implements EdgeEventService { throw new TbRateLimitsException(EntityType.EDGE); } edgeEventValidator.validate(edgeEvent, EdgeEvent::getTenantId); - - ListenableFuture saveFuture = edgeEventDao.saveAsync(edgeEvent); - - Futures.addCallback(saveFuture, new FutureCallback<>() { - @Override - public void onSuccess(Void result) { - eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(edgeEvent.getTenantId()) - .entity(edgeEvent).entityId(edgeEvent.getEdgeId()).build()); - } - - @Override - public void onFailure(@NotNull Throwable throwable) {} - }, edgeEventExecutor); - - return saveFuture; - } - - @Override - public PageData findEdgeEvents(TenantId tenantId, EdgeId edgeId, Long seqIdStart, Long seqIdEnd, TimePageLink pageLink) { - return edgeEventDao.findEdgeEvents(tenantId.getId(), edgeId, seqIdStart, seqIdEnd, pageLink); } - @Override - public void cleanupEvents(long ttl) { - edgeEventDao.cleanupEvents(ttl); - } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/edge/DefaultEdgeSynchronizationManager.java b/dao/src/main/java/org/thingsboard/server/dao/edge/DefaultEdgeSynchronizationManager.java index 1fc9bdb226..c1016c571d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/edge/DefaultEdgeSynchronizationManager.java +++ b/dao/src/main/java/org/thingsboard/server/dao/edge/DefaultEdgeSynchronizationManager.java @@ -20,10 +20,11 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.id.EdgeId; -@Component @Slf4j +@Getter +@Component public class DefaultEdgeSynchronizationManager implements EdgeSynchronizationManager { - @Getter private final ThreadLocal edgeId = new ThreadLocal<>(); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeDao.java b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeDao.java index be876cb5e0..e012766a8f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeDao.java @@ -40,6 +40,8 @@ public interface EdgeDao extends Dao { EdgeInfo findEdgeInfoById(TenantId tenantId, UUID edgeId); + PageData findEdgeIdsByTenantId(UUID tenantId, PageLink pageLink); + PageData findEdgesByTenantId(UUID tenantId, PageLink pageLink); PageData findEdgesByTenantIdAndType(UUID tenantId, String type, PageLink pageLink); diff --git a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeEventDao.java b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeEventDao.java index 1f9562724d..572733d75b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeEventDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeEventDao.java @@ -56,6 +56,4 @@ public interface EdgeEventDao extends Dao { */ void cleanupEvents(long ttl); - void migrateEdgeEvents(); - } 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 1e0202bf79..c9b465b178 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 @@ -261,6 +261,14 @@ public class EdgeServiceImpl extends AbstractCachedEntityService findEdgeIdsByTenantId(TenantId tenantId, PageLink pageLink) { + log.trace("Executing findEdgeIdsByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink); + validateId(tenantId, id -> INCORRECT_TENANT_ID + id); + validatePageLink(pageLink); + return edgeDao.findEdgeIdsByTenantId(tenantId.getId(), pageLink); + } + @Override public PageData findEdgesByTenantId(TenantId tenantId, PageLink pageLink) { log.trace("Executing findEdgesByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink); 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 new file mode 100644 index 0000000000..b021ac90df --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/edge/PostgresEdgeEventService.java @@ -0,0 +1,81 @@ +/** + * Copyright © 2016-2024 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.dao.edge; + +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Service; +import org.thingsboard.common.util.ThingsBoardThreadFactory; +import org.thingsboard.server.common.data.edge.EdgeEvent; +import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +@Slf4j +@Service +@RequiredArgsConstructor +@ConditionalOnExpression("'${queue.type:null}'!='kafka'") +public class PostgresEdgeEventService extends BaseEdgeEventService { + + private final EdgeEventDao edgeEventDao; + private final ApplicationEventPublisher eventPublisher; + + private ExecutorService edgeEventExecutor; + + @PostConstruct + public void initExecutor() { + edgeEventExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("edge-event-service")); + } + + @PreDestroy + public void shutdownExecutor() { + if (edgeEventExecutor != null) { + edgeEventExecutor.shutdown(); + } + } + + @Override + public ListenableFuture saveAsync(EdgeEvent edgeEvent) { + validateEdgeEvent(edgeEvent); + ListenableFuture saveFuture = edgeEventDao.saveAsync(edgeEvent); + + Futures.addCallback(saveFuture, new FutureCallback<>() { + @Override + public void onSuccess(Void result) { + eventPublisher.publishEvent(SaveEntityEvent.builder() + .tenantId(edgeEvent.getTenantId()) + .entityId(edgeEvent.getEdgeId()) + .entity(edgeEvent) + .build()); + } + + @Override + public void onFailure(@NotNull Throwable throwable) {} + }, edgeEventExecutor); + + return saveFuture; + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppBundleServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppBundleServiceImpl.java index 4997ad8d30..e2c817e0c5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppBundleServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppBundleServiceImpl.java @@ -71,8 +71,8 @@ public class MobileAppBundleServiceImpl extends AbstractEntityService implements return savedMobileApp; } catch (Exception e) { checkConstraintViolation(e, - Map.of("android_app_id_unq_key", "Android mobile app already exists in another bundle!", - "ios_app_id_unq_key", "IOS mobile app already exists in another bundle!")); + Map.of("mobile_app_bundle_android_app_id_key", "Android mobile app is already configured in another bundle!", + "mobile_app_bundle_ios_app_id_key", "IOS mobile app is already configured in another bundle!")); throw e; } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/EdgeEventEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/EdgeEventEntity.java index 1489a40a27..9816938706 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/EdgeEventEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/EdgeEventEntity.java @@ -129,4 +129,5 @@ public class EdgeEventEntity extends BaseSqlEntity implements BaseEnt private static long getTs(UUID uuid) { return (uuid.timestamp() - EPOCH_DIFF) / 10000; } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java index 9b747766c9..16088e83c9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java @@ -381,6 +381,16 @@ public class DefaultNotifications { .icon("warning").color(RED_COLOR) .build(); + public static final DefaultNotification databaseTypeDeprecation = DefaultNotification.builder() + .name("Database type deprecation") + .type(NotificationType.GENERAL) + .subject("WARNING: ${databaseType} deprecation") + .text("Starting with ThingsBoard 4.0, ${databaseType} will no longer be supported as a storage provider. " + + "Please migrate to Cassandra or PostgreSQL.") + .icon("warning") + .color(RED_COLOR) + .build(); + private final NotificationTemplateService templateService; private final NotificationRuleService ruleService; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/edge/EdgeRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/edge/EdgeRepository.java index b0c6a47783..79c3e93170 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/edge/EdgeRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/edge/EdgeRepository.java @@ -42,6 +42,12 @@ public interface EdgeRepository extends JpaRepository { "WHERE d.id = :edgeId") EdgeInfoEntity findEdgeInfoById(@Param("edgeId") UUID edgeId); + @Query("SELECT d.id FROM EdgeEntity d WHERE d.tenantId = :tenantId " + + "AND (:textSearch IS NULL OR ilike(d.name, CONCAT('%', :textSearch, '%')) = true)") + Page findIdsByTenantId(@Param("tenantId") UUID tenantId, + @Param("textSearch") String textSearch, + Pageable pageable); + @Query("SELECT d FROM EdgeEntity d WHERE d.tenantId = :tenantId " + "AND (:textSearch IS NULL OR ilike(d.name, CONCAT('%', :textSearch, '%')) = true)") Page findByTenantId(@Param("tenantId") UUID tenantId, @@ -93,9 +99,9 @@ public interface EdgeRepository extends JpaRepository { "AND a.customerId = :customerId " + "AND (:textSearch IS NULL OR ilike(a.name, CONCAT('%', :textSearch, '%')) = true)") Page findEdgeInfosByTenantIdAndCustomerId(@Param("tenantId") UUID tenantId, - @Param("customerId") UUID customerId, - @Param("textSearch") String textSearch, - Pageable pageable); + @Param("customerId") UUID customerId, + @Param("textSearch") String textSearch, + Pageable pageable); @Query("SELECT new org.thingsboard.server.dao.model.sql.EdgeInfoEntity(a, c.title, c.additionalInfo) " + "FROM EdgeEntity a " + @@ -105,10 +111,10 @@ public interface EdgeRepository extends JpaRepository { "AND a.type = :type " + "AND (:textSearch IS NULL OR ilike(a.name, CONCAT('%', :textSearch, '%')) = true)") Page findEdgeInfosByTenantIdAndCustomerIdAndType(@Param("tenantId") UUID tenantId, - @Param("customerId") UUID customerId, - @Param("type") String type, - @Param("textSearch") String textSearch, - Pageable pageable); + @Param("customerId") UUID customerId, + @Param("type") String type, + @Param("textSearch") String textSearch, + Pageable pageable); @Query("SELECT ee FROM EdgeEntity ee, RelationEntity re WHERE ee.tenantId = :tenantId " + "AND ee.id = re.fromId AND re.fromType = 'EDGE' AND re.relationTypeGroup = 'EDGE' " + @@ -125,10 +131,10 @@ public interface EdgeRepository extends JpaRepository { "AND re.relationType = 'Contains' AND re.toId = :entityId AND re.toType = :entityType " + "AND (:textSearch IS NULL OR ilike(ee.name, CONCAT('%', :textSearch, '%')) = true)") Page findIdsByTenantIdAndEntityId(@Param("tenantId") UUID tenantId, - @Param("entityId") UUID entityId, - @Param("entityType") String entityType, - @Param("textSearch") String textSearch, - Pageable pageable); + @Param("entityId") UUID entityId, + @Param("entityType") String entityType, + @Param("textSearch") String textSearch, + Pageable pageable); @Query("SELECT ee FROM EdgeEntity ee, TenantEntity te WHERE ee.tenantId = te.id AND te.tenantProfileId = :tenantProfileId ") Page findByTenantProfileId(@Param("tenantProfileId") UUID tenantProfileId, diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaBaseEdgeEventDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaBaseEdgeEventDao.java index d9d2282fca..065a057160 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaBaseEdgeEventDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaBaseEdgeEventDao.java @@ -198,36 +198,6 @@ public class JpaBaseEdgeEventDao extends JpaPartitionedAbstractDao 0 ? System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(edgeEventsTtl) : 1629158400000L; - - long currentTime = System.currentTimeMillis(); - var partitionStepInMs = TimeUnit.HOURS.toMillis(partitionSizeInHours); - long numberOfPartitions = (currentTime - startTime) / partitionStepInMs; - - if (numberOfPartitions > 1000) { - String error = "Please adjust your edge event partitioning configuration. Configuration with partition size " + - "of " + partitionSizeInHours + " hours and corresponding TTL will use " + numberOfPartitions + " " + - "(> 1000) partitions which is not recommended!"; - log.error(error); - throw new RuntimeException(error); - } - - while (startTime < currentTime) { - var endTime = startTime + partitionStepInMs; - log.info("Migrating edge event for time period: {} - {}", startTime, endTime); - callMigrationFunction(startTime, endTime, partitionStepInMs); - startTime = endTime; - } - log.info("Event edge migration finished"); - jdbcTemplate.execute("DROP TABLE IF EXISTS old_edge_event"); - } - - private void callMigrationFunction(long startTime, long endTime, long partitionSIzeInMs) { - jdbcTemplate.update("CALL migrate_edge_event(?, ?, ?)", startTime, endTime, partitionSIzeInMs); - } - @Override public void createPartition(EdgeEventEntity entity) { partitioningRepository.createPartitionIfNotExists(TABLE_NAME, entity.getCreatedTime(), TimeUnit.HOURS.toMillis(partitionSizeInHours)); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaEdgeDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaEdgeDao.java index 3c2d64825f..b02a28fee4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaEdgeDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaEdgeDao.java @@ -64,6 +64,15 @@ public class JpaEdgeDao extends JpaAbstractDao implements Edge return DaoUtil.getData(edgeRepository.findEdgeInfoById(edgeId)); } + @Override + public PageData findEdgeIdsByTenantId(UUID tenantId, PageLink pageLink) { + return DaoUtil.pageToPageData( + edgeRepository.findIdsByTenantId( + tenantId, + pageLink.getTextSearch(), + DaoUtil.toPageable(pageLink))).mapData(EdgeId::fromUUID); + } + @Override public PageData findEdgesByTenantId(UUID tenantId, PageLink pageLink) { return DaoUtil.toPageData( diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java index dd1b4278d7..36ae779edd 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java @@ -663,7 +663,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { .append("nr.").append(fromOrTo).append("_id").append(" = re.").append(toOrFrom).append("_id") .append(" and ") .append("nr.").append(fromOrTo).append("_type").append(" = re.").append(toOrFrom).append("_type"); - + notExistsPart.append(" and nr.relation_type_group = 'COMMON'"); // hit the index, the same condition are on the recursive query notExistsPart.append(")"); whereFilter += " and ( r_int.lvl = " + entityFilter.getMaxLevel() + " OR " + notExistsPart.toString() + ")"; } @@ -755,7 +755,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { .append("nr.").append(fromOrTo).append("_type").append(" = re.").append(toOrFrom).append("_type") .append(" and ") .append(whereFilter.toString().replaceAll("re\\.", "nr\\.")); - + notExistsPart.append(" and nr.relation_type_group = 'COMMON'"); // hit the index, the same condition are on the recursive query notExistsPart.append(")"); whereFilter.append(" and ( r_int.lvl = ").append(entityFilter.getMaxLevel()).append(" OR ").append(notExistsPart.toString()).append(")"); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java index f61854f062..206d55354d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java @@ -292,6 +292,16 @@ public class UserServiceImpl extends AbstractCachedEntityService saveEdgeEventWithProvidedTime(long time, EdgeId edgeId, EntityId entityId, TenantId tenantId) throws Exception { diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java index c298d4fea3..8c26be9b45 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java @@ -28,6 +28,7 @@ import io.netty.handler.codec.mqtt.MqttMessage; import io.netty.handler.codec.mqtt.MqttQoS; import io.netty.handler.codec.mqtt.MqttReasonCodeAndPropertiesVariableHeader; import io.netty.handler.codec.mqtt.MqttReasonCodes; +import io.netty.handler.codec.mqtt.MqttSubAckMessage; import io.netty.handler.codec.mqtt.MqttVersion; import lombok.Data; import lombok.extern.slf4j.Slf4j; @@ -491,6 +492,48 @@ public class MqttClientTest extends AbstractContainerTest { updateDeviceProfileWithProvisioningStrategy(deviceProfile, DeviceProfileProvisionType.DISABLED); } + @Test + public void provisionRequestForCheckSubAckReceived() throws Exception { + + DeviceProfile deviceProfile = testRestClient.getDeviceProfileById(device.getDeviceProfileId()); + deviceProfile = updateDeviceProfileWithProvisioningStrategy(deviceProfile, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES); + + MqttMessageListener listener = new MqttMessageListener(); + MqttClient mqttClient = getMqttClient("provision", listener, MqttVersion.MQTT_5); + final MqttReasonCodes.SubAck[] subAckResult = new MqttReasonCodes.SubAck[1]; + mqttClient.setCallback(new MqttClientCallback() { + @Override + public void connectionLost(Throwable cause) { + } + + @Override + public void onSuccessfulReconnect() { + } + + @Override + public void onSubAck(MqttSubAckMessage subAckMessage) { + subAckResult[0] = subAckMessage.payload().typedReasonCodes().get(0); + } + } + ); + + mqttClient.on("/provision/response", listener, MqttQoS.AT_LEAST_ONCE).get(3 * timeoutMultiplier, TimeUnit.SECONDS); + TimeUnit.SECONDS.sleep(2 * timeoutMultiplier); + assertThat(subAckResult[0]).isNotNull(); + assertThat(MqttReasonCodes.SubAck.GRANTED_QOS_1.equals(subAckResult[0])); + + subAckResult[0] = null; + mqttClient.on("v1/devices/me/attributes", listener, MqttQoS.AT_LEAST_ONCE).get(3 * timeoutMultiplier, TimeUnit.SECONDS); + TimeUnit.SECONDS.sleep(2 * timeoutMultiplier); + assertThat(subAckResult[0]).isNotNull(); + assertThat(MqttReasonCodes.SubAck.TOPIC_FILTER_INVALID.equals(subAckResult[0])); + + testRestClient.deleteDeviceIfExists(device.getId()); + updateDeviceProfileWithProvisioningStrategy(deviceProfile, DeviceProfileProvisionType.DISABLED); + } + + + @Test public void provisionRequestForDeviceWithDisabledProvisioningStrategy() throws Exception { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbSaveToCustomCassandraTableNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbSaveToCustomCassandraTableNode.java index 3b2fc82874..a0dbc5da97 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbSaveToCustomCassandraTableNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbSaveToCustomCassandraTableNode.java @@ -21,6 +21,8 @@ import com.datastax.oss.driver.api.core.cql.BoundStatement; import com.datastax.oss.driver.api.core.cql.BoundStatementBuilder; import com.datastax.oss.driver.api.core.cql.PreparedStatement; import com.datastax.oss.driver.api.core.cql.Statement; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.base.Function; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; @@ -38,6 +40,7 @@ import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.rule.RuleChainType; +import org.thingsboard.server.common.data.util.TbPair; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.dao.cassandra.CassandraCluster; import org.thingsboard.server.dao.cassandra.guava.GuavaSession; @@ -57,6 +60,7 @@ import static org.thingsboard.common.util.DonAsynchron.withCallback; @RuleNode(type = ComponentType.ACTION, name = "save to custom table", configClazz = TbSaveToCustomCassandraTableNodeConfiguration.class, + version = 1, nodeDescription = "Node stores data from incoming Message payload to the Cassandra database into the predefined custom table" + " that should have cs_tb_ prefix, to avoid the data insertion to the common TB tables.
" + "Note: rule node can be used only for Cassandra DB.", @@ -87,11 +91,13 @@ public class TbSaveToCustomCassandraTableNode implements TbNode { config = TbNodeUtils.convert(configuration, TbSaveToCustomCassandraTableNodeConfiguration.class); cassandraCluster = ctx.getCassandraCluster(); if (cassandraCluster == null) { - throw new RuntimeException("Unable to connect to Cassandra database"); - } else { - startExecutor(); - saveStmt = getSaveStmt(); + throw new TbNodeException("Unable to connect to Cassandra database", true); + } + if (!isTableExists()) { + throw new TbNodeException("Table '" + TABLE_PREFIX + config.getTableName() + "' does not exist in Cassandra cluster."); } + startExecutor(); + saveStmt = getSaveStmt(); } @Override @@ -115,6 +121,12 @@ public class TbSaveToCustomCassandraTableNode implements TbNode { } } + private boolean isTableExists() { + var keyspaceMdOpt = getSession().getMetadata().getKeyspace(cassandraCluster.getKeyspaceName()); + return keyspaceMdOpt.map(keyspaceMetadata -> + keyspaceMetadata.getTable(TABLE_PREFIX + config.getTableName()).isPresent()).orElse(false); + } + private PreparedStatement prepare(String query) { return getSession().prepare(query); } @@ -127,10 +139,10 @@ public class TbSaveToCustomCassandraTableNode implements TbNode { return session; } - private PreparedStatement getSaveStmt() { + private PreparedStatement getSaveStmt() throws TbNodeException { fieldsMap = config.getFieldsMapping(); if (fieldsMap.isEmpty()) { - throw new RuntimeException("Fields(key,value) map is empty!"); + throw new TbNodeException("Fields(key,value) map is empty!", true); } else { return prepareStatement(new ArrayList<>(fieldsMap.values())); } @@ -163,16 +175,19 @@ public class TbSaveToCustomCassandraTableNode implements TbNode { query.append("?, "); } } + if (config.getDefaultTtl() > 0) { + query.append(" USING TTL ?"); + } return query.toString(); } private ListenableFuture save(TbMsg msg, TbContext ctx) { JsonElement data = JsonParser.parseString(msg.getData()); if (!data.isJsonObject()) { - throw new IllegalStateException("Invalid message structure, it is not a JSON Object:" + data); + throw new IllegalStateException("Invalid message structure, it is not a JSON Object: " + data); } else { JsonObject dataAsObject = data.getAsJsonObject(); - BoundStatementBuilder stmtBuilder = new BoundStatementBuilder(saveStmt.bind()); + BoundStatementBuilder stmtBuilder = getStmtBuilder(); AtomicInteger i = new AtomicInteger(0); fieldsMap.forEach((key, value) -> { if (key.equals(ENTITY_ID)) { @@ -197,17 +212,24 @@ public class TbSaveToCustomCassandraTableNode implements TbNode { } else if (dataKeyElement.isJsonObject()) { stmtBuilder.setString(i.get(), dataKeyElement.getAsJsonObject().toString()); } else { - throw new IllegalStateException("Message data key: '" + key + "' with value: '" + value + "' is not a JSON Object or JSON Primitive!"); + throw new IllegalStateException("Message data key: '" + key + "' with value: '" + dataKeyElement + "' is not a JSON Object or JSON Primitive!"); } } else { throw new RuntimeException("Message data doesn't contain key: " + "'" + key + "'!"); } i.getAndIncrement(); }); + if (config.getDefaultTtl() > 0) { + stmtBuilder.setInt(i.get(), config.getDefaultTtl()); + } return getFuture(executeAsyncWrite(ctx, stmtBuilder.build()), rs -> null); } } + BoundStatementBuilder getStmtBuilder() { + return new BoundStatementBuilder(saveStmt.bind()); + } + private TbResultSetFuture executeAsyncWrite(TbContext ctx, Statement statement) { return executeAsync(ctx, statement, defaultWriteLevel); } @@ -240,4 +262,20 @@ public class TbSaveToCustomCassandraTableNode implements TbNode { }, readResultsProcessingExecutor); } + @Override + public TbPair upgrade(int fromVersion, JsonNode oldConfiguration) throws TbNodeException { + boolean hasChanges = false; + switch (fromVersion) { + case 0: + if (!oldConfiguration.has("defaultTtl")) { + hasChanges = true; + ((ObjectNode) oldConfiguration).put("defaultTtl", 0); + } + break; + default: + break; + } + return new TbPair<>(hasChanges, oldConfiguration); + } + } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbSaveToCustomCassandraTableNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbSaveToCustomCassandraTableNodeConfiguration.java index 0a5f153192..fefe871f82 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbSaveToCustomCassandraTableNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbSaveToCustomCassandraTableNodeConfiguration.java @@ -27,11 +27,13 @@ public class TbSaveToCustomCassandraTableNodeConfiguration implements NodeConfig private String tableName; private Map fieldsMapping; + private int defaultTtl; @Override public TbSaveToCustomCassandraTableNodeConfiguration defaultConfiguration() { TbSaveToCustomCassandraTableNodeConfiguration configuration = new TbSaveToCustomCassandraTableNodeConfiguration(); + configuration.setDefaultTtl(0); configuration.setTableName(""); Map map = new HashMap<>(); map.put("", ""); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbSaveToCustomCassandraTableNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbSaveToCustomCassandraTableNodeTest.java new file mode 100644 index 0000000000..7df2a982de --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbSaveToCustomCassandraTableNodeTest.java @@ -0,0 +1,396 @@ +/** + * Copyright © 2016-2024 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.rule.engine.action; + +import com.datastax.oss.driver.api.core.DefaultConsistencyLevel; +import com.datastax.oss.driver.api.core.ProtocolVersion; +import com.datastax.oss.driver.api.core.cql.BoundStatement; +import com.datastax.oss.driver.api.core.cql.BoundStatementBuilder; +import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; +import com.datastax.oss.driver.api.core.cql.PreparedStatement; +import com.datastax.oss.driver.api.core.metadata.Metadata; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; +import com.datastax.oss.driver.api.core.metadata.schema.TableMetadata; +import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; +import com.google.common.util.concurrent.SettableFuture; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.common.util.ListeningExecutor; +import org.thingsboard.rule.engine.AbstractRuleNodeUpgradeTest; +import org.thingsboard.rule.engine.TestDbCallbackExecutor; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNode; +import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; +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.cassandra.CassandraCluster; +import org.thingsboard.server.dao.cassandra.guava.GuavaSession; +import org.thingsboard.server.dao.nosql.CassandraStatementTask; +import org.thingsboard.server.dao.nosql.TbResultSet; +import org.thingsboard.server.dao.nosql.TbResultSetFuture; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import java.util.stream.Stream; + +import static java.util.Collections.emptyMap; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.awaitility.Awaitility.await; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyDouble; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.never; +import static org.mockito.BDDMockito.spy; +import static org.mockito.BDDMockito.then; +import static org.mockito.BDDMockito.willAnswer; + +@ExtendWith(MockitoExtension.class) +public class TbSaveToCustomCassandraTableNodeTest extends AbstractRuleNodeUpgradeTest { + + private final DeviceId DEVICE_ID = new DeviceId(UUID.fromString("ac4ca02e-2ae6-404a-8f7e-c4ae31c56aa7")); + private final TenantId TENANT_ID = TenantId.fromUUID(UUID.fromString("64ad971e-9cfa-49e4-9f59-faa1a2350c6e")); + + private final ListeningExecutor dbCallbackExecutor = new TestDbCallbackExecutor(); + + private TbSaveToCustomCassandraTableNode node; + private TbSaveToCustomCassandraTableNodeConfiguration config; + + @Mock + private TbContext ctxMock; + @Mock + private CassandraCluster cassandraClusterMock; + @Mock + private GuavaSession sessionMock; + @Mock + private PreparedStatement preparedStatementMock; + @Mock + private BoundStatement boundStatementMock; + @Mock + private BoundStatementBuilder boundStatementBuilderMock; + @Mock + private ColumnDefinitions columnDefinitionsMock; + @Mock + private CodecRegistry codecRegistryMock; + @Mock + private ProtocolVersion protocolVersionMock; + @Mock + private Node nodeMock; + @Mock + private Metadata metadataMock; + @Mock + private KeyspaceMetadata keyspaceMetadataMock; + @Mock + private TableMetadata tableMetadataMock; + + @BeforeEach + public void setUp() { + node = spy(new TbSaveToCustomCassandraTableNode()); + config = new TbSaveToCustomCassandraTableNodeConfiguration().defaultConfiguration(); + } + + @AfterEach + public void tearDown() { + node.destroy(); + } + + @Test + public void verifyDefaultConfig() { + assertThat(config.getTableName()).isEqualTo(""); + assertThat(config.getFieldsMapping()).isEqualTo(Map.of("", "")); + assertThat(config.getDefaultTtl()).isEqualTo(0); + } + + @Test + public void givenCassandraClusterIsMissing_whenInit_thenThrowsException() { + var configuration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); + assertThatThrownBy(() -> node.init(ctxMock, configuration)) + .isInstanceOf(TbNodeException.class) + .hasMessage("Unable to connect to Cassandra database") + .extracting(e -> ((TbNodeException) e).isUnrecoverable()) + .isEqualTo(true); + } + + @Test + public void givenTableDoesNotExist_whenInit_thenThrowsException() { + config.setTableName("test_table"); + var configuration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); + + mockCassandraCluster(); + given(keyspaceMetadataMock.getTable(anyString())).willReturn(Optional.empty()); + + assertThatThrownBy(() -> node.init(ctxMock, configuration)) + .isInstanceOf(TbNodeException.class) + .hasMessage("Table 'cs_tb_test_table' does not exist in Cassandra cluster.") + .extracting(e -> ((TbNodeException) e).isUnrecoverable()) + .isEqualTo(false); + } + + @Test + public void givenFieldsMapIsEmpty_whenInit_thenThrowsException() { + config.setTableName("test_table"); + config.setFieldsMapping(emptyMap()); + var configuration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); + + mockCassandraCluster(); + + assertThatThrownBy(() -> node.init(ctxMock, configuration)) + .isInstanceOf(TbNodeException.class) + .hasMessage("Fields(key,value) map is empty!"); + } + + @Test + public void givenInvalidMessageStructure_whenOnMsg_thenThrowsException() throws TbNodeException { + config.setTableName("temperature_sensor"); + config.setFieldsMapping(Map.of("temp", "temperature")); + + mockOnInit(); + + node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DEVICE_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); + assertThatThrownBy(() -> node.onMsg(ctxMock, msg)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("Invalid message structure, it is not a JSON Object: " + null); + } + + @Test + public void givenDataKeyIsMissingInMsg_whenOnMsg_thenThrowsException() throws TbNodeException { + config.setTableName("temperature_sensor"); + config.setFieldsMapping(Map.of("temp", "temperature")); + + mockOnInit(); + mockBoundStatement(); + + node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + String data = """ + { + "humidity": 77 + } + """; + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DEVICE_ID, TbMsgMetaData.EMPTY, data); + assertThatThrownBy(() -> node.onMsg(ctxMock, msg)) + .isInstanceOf(RuntimeException.class) + .hasMessage("Message data doesn't contain key: 'temp'!"); + } + + @Test + public void givenUnsupportedData_whenOnMsg_thenThrowsException() throws TbNodeException { + config.setTableName("temperature_sensor"); + config.setFieldsMapping(Map.of("temp", "temperature")); + + mockOnInit(); + mockBoundStatement(); + + node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + String data = """ + { + "temp": [value] + } + """; + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DEVICE_ID, TbMsgMetaData.EMPTY, data); + assertThatThrownBy(() -> node.onMsg(ctxMock, msg)) + .isInstanceOf(RuntimeException.class) + .hasMessage("Message data key: 'temp' with value: '[\"value\"]' is not a JSON Object or JSON Primitive!"); + } + + @ParameterizedTest + @MethodSource + public void givenTtl_whenOnMsg_thenVerifyStatement(int ttlFromConfig, + String expectedQuery, + Consumer verifyBuilder) throws TbNodeException { + config.setTableName("readings"); + config.setFieldsMapping(Map.of("$entityId", "entityIdTableColumn")); + config.setDefaultTtl(ttlFromConfig); + + mockOnInit(); + willAnswer(invocation -> boundStatementBuilderMock).given(node).getStmtBuilder(); + given(boundStatementBuilderMock.setUuid(anyInt(), any(UUID.class))).willReturn(boundStatementBuilderMock); + given(boundStatementBuilderMock.build()).willReturn(boundStatementMock); + mockSubmittingCassandraTask(); + + node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DEVICE_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); + node.onMsg(ctxMock, msg); + + then(sessionMock).should().prepare(expectedQuery); + verifyBuilder.accept(boundStatementBuilderMock); + } + + private static Stream givenTtl_whenOnMsg_thenVerifyStatement() { + return Stream.of( + Arguments.of(0, "INSERT INTO cs_tb_readings(entityIdTableColumn) VALUES(?)", + (Consumer) builder -> { + then(builder).should(never()).setInt(anyInt(), anyInt()); + }), + Arguments.of(20, "INSERT INTO cs_tb_readings(entityIdTableColumn) VALUES(?) USING TTL ?", + (Consumer) builder -> { + then(builder).should().setInt(1, 20); + }) + ); + } + + @Test + public void givenValidMsgStructure_whenOnMsg_thenVerifyMatchOfValuesInsertionOrderIntoStatementAndSaveToCustomCassandraTable() throws TbNodeException { + config.setDefaultTtl(25); + config.setTableName("readings"); + Map mappings = Map.of( + "$entityId", "entityIdTableColumn", + "doubleField", "doubleTableColumn", + "longField", "longTableColumn", + "booleanField", "booleanTableColumn", + "stringField", "stringTableColumn", + "jsonField", "jsonTableColumn" + ); + config.setFieldsMapping(mappings); + + mockOnInit(); + mockBoundStatementBuilder(); + mockSubmittingCassandraTask(); + + node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + String data = """ + { + "doubleField": 22.5, + "longField": 56, + "booleanField": true, + "stringField": "some string", + "jsonField": { + "key": "value" + } + } + """; + TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DEVICE_ID, TbMsgMetaData.EMPTY, data); + node.onMsg(ctxMock, msg); + + verifySettingStatementBuilder(); + ArgumentCaptor taskCaptor = ArgumentCaptor.forClass(CassandraStatementTask.class); + then(ctxMock).should().submitCassandraWriteTask(taskCaptor.capture()); + CassandraStatementTask task = taskCaptor.getValue(); + assertThat(task.getTenantId()).isEqualTo(TENANT_ID); + assertThat(task.getSession()).isEqualTo(sessionMock); + assertThat(task.getStatement()).isEqualTo(boundStatementMock); + await().atMost(1, TimeUnit.SECONDS).untilAsserted( + () -> then(ctxMock).should().tellSuccess(msg) + ); + } + + @Override + protected TbNode getTestNode() { + return node; + } + + private static Stream givenFromVersionAndConfig_whenUpgrade_thenVerifyHasChangesAndConfig() { + return Stream.of( + // config for version 1 with upgrade from version 0 + Arguments.of(0, + "{\"tableName\":\"\",\"fieldsMapping\":{\"\":\"\"}}", + true, + "{\"tableName\":\"\",\"fieldsMapping\":{\"\":\"\"},\"defaultTtl\":0}" + ), + // default config for version 1 with upgrade from version 1 + Arguments.of(1, + "{\"tableName\":\"\",\"fieldsMapping\":{\"\":\"\"},\"defaultTtl\":0}", + false, + "{\"tableName\":\"\",\"fieldsMapping\":{\"\":\"\"},\"defaultTtl\":0}" + ) + ); + } + + private void mockOnInit() { + mockCassandraCluster(); + given(cassandraClusterMock.getDefaultWriteConsistencyLevel()).willReturn(DefaultConsistencyLevel.ONE); + given(sessionMock.prepare(anyString())).willReturn(preparedStatementMock); + } + + private void mockCassandraCluster() { + given(ctxMock.getCassandraCluster()).willReturn(cassandraClusterMock); + given(cassandraClusterMock.getSession()).willReturn(sessionMock); + given(sessionMock.getMetadata()).willReturn(metadataMock); + given(cassandraClusterMock.getKeyspaceName()).willReturn("test_keyspace"); + given(metadataMock.getKeyspace(anyString())).willReturn(Optional.of(keyspaceMetadataMock)); + given(keyspaceMetadataMock.getTable(anyString())).willReturn(Optional.of(tableMetadataMock)); + } + + private void mockBoundStatement() { + given(preparedStatementMock.bind()).willReturn(boundStatementMock); + given(boundStatementMock.getPreparedStatement()).willReturn(preparedStatementMock); + given(preparedStatementMock.getVariableDefinitions()).willReturn(columnDefinitionsMock); + given(boundStatementMock.codecRegistry()).willReturn(codecRegistryMock); + given(boundStatementMock.protocolVersion()).willReturn(protocolVersionMock); + given(boundStatementMock.getNode()).willReturn(nodeMock); + } + + private void mockBoundStatementBuilder() { + willAnswer(invocation -> boundStatementBuilderMock).given(node).getStmtBuilder(); + given(boundStatementBuilderMock.setUuid(anyInt(), any(UUID.class))).willReturn(boundStatementBuilderMock); + given(boundStatementBuilderMock.setDouble(anyInt(), anyDouble())).willReturn(boundStatementBuilderMock); + given(boundStatementBuilderMock.setLong(anyInt(), anyLong())).willReturn(boundStatementBuilderMock); + given(boundStatementBuilderMock.setBoolean(anyInt(), anyBoolean())).willReturn(boundStatementBuilderMock); + given(boundStatementBuilderMock.setString(anyInt(), anyString())).willReturn(boundStatementBuilderMock); + given(boundStatementBuilderMock.setInt(anyInt(), anyInt())).willReturn(boundStatementBuilderMock); + given(boundStatementBuilderMock.build()).willReturn(boundStatementMock); + } + + private void mockSubmittingCassandraTask() { + given(ctxMock.getTenantId()).willReturn(TENANT_ID); + willAnswer(invocation -> { + SettableFuture mainFuture = SettableFuture.create(); + mainFuture.set(new TbResultSet(null, null, null)); + return new TbResultSetFuture(mainFuture); + }).given(ctxMock).submitCassandraWriteTask(any()); + given(ctxMock.getDbCallbackExecutor()).willReturn(dbCallbackExecutor); + } + + private void verifySettingStatementBuilder() { + Map fieldsMap = (Map) ReflectionTestUtils.getField(node, "fieldsMap"); + List values = new ArrayList<>(fieldsMap.values()); + then(boundStatementBuilderMock).should().setUuid(values.indexOf("entityIdTableColumn"), DEVICE_ID.getId()); + then(boundStatementBuilderMock).should().setDouble(values.indexOf("doubleTableColumn"), 22.5); + then(boundStatementBuilderMock).should().setLong(values.indexOf("longTableColumn"), 56L); + then(boundStatementBuilderMock).should().setBoolean(values.indexOf("booleanTableColumn"), true); + then(boundStatementBuilderMock).should().setString(values.indexOf("stringTableColumn"), "some string"); + then(boundStatementBuilderMock).should().setString(values.indexOf("jsonTableColumn"), "{\"key\":\"value\"}"); + then(boundStatementBuilderMock).should().setInt(values.size(), 25); + } + +} diff --git a/ui-ngx/package.json b/ui-ngx/package.json index 0ac7b1e4e2..0eff3bbd8d 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -13,23 +13,22 @@ }, "private": true, "dependencies": { - "@angular/animations": "18.2.10", - "@angular/cdk": "18.2.11", - "@angular/common": "18.2.10", - "@angular/compiler": "18.2.10", - "@angular/core": "18.2.10", + "@angular/animations": "18.2.13", + "@angular/cdk": "18.2.14", + "@angular/common": "18.2.13", + "@angular/compiler": "18.2.13", + "@angular/core": "18.2.13", "@angular/flex-layout": "^15.0.0-beta.42", - "@angular/forms": "18.2.10", - "@angular/material": "18.2.11", - "@angular/platform-browser": "18.2.10", - "@angular/platform-browser-dynamic": "18.2.10", - "@angular/router": "18.2.10", + "@angular/forms": "18.2.13", + "@angular/material": "18.2.14", + "@angular/platform-browser": "18.2.13", + "@angular/platform-browser-dynamic": "18.2.13", + "@angular/router": "18.2.13", "@auth0/angular-jwt": "^5.2.0", "@emotion/react": "11.13.3", "@emotion/styled": "11.13.0", "@flowjs/flow.js": "^2.14.1", "@flowjs/ngx-flow": "18.0.1", - "@fortawesome/fontawesome-svg-core": "^6.6.0", "@geoman-io/leaflet-geoman-free": "2.17.0", "@iplab/ngx-color-picker": "^18.0.1", "@mat-datetimepicker/core": "~14.0.0", @@ -49,12 +48,12 @@ "@svgdotjs/svg.js": "^3.2.4", "@svgdotjs/svg.panzoom.js": "^2.1.2", "@tinymce/tinymce-angular": "^8.0.1", - "ace-builds": "1.36.2", + "ace-builds": "1.36.5", "ace-diff": "^3.0.3", "angular-gridster2": "~18.0.1", "angular2-hotkeys": "^16.0.1", "canvas-gauges": "^2.1.7", - "core-js": "^3.38.1", + "core-js": "^3.39.0", "dayjs": "1.11.13", "echarts": "https://github.com/thingsboard/echarts/archive/5.5.0-TB.tar.gz", "flot": "https://github.com/thingsboard/flot.git#0.9-work", @@ -62,7 +61,7 @@ "font-awesome": "^4.7.0", "html2canvas": "^1.4.1", "jquery": "^3.7.1", - "jquery.terminal": "^2.43.1", + "jquery.terminal": "^2.44.1", "js-beautify": "1.15.1", "json-schema-defaults": "^0.4.0", "jstree": "^3.3.17", @@ -73,7 +72,7 @@ "leaflet-providers": "2.0.0", "leaflet.gridlayer.googlemutant": "0.14.1", "leaflet.markercluster": "1.5.3", - "libphonenumber-js": "^1.11.10", + "libphonenumber-js": "^1.11.15", "marked": "~12.0.2", "moment": "^2.30.1", "moment-timezone": "^0.5.45", @@ -83,7 +82,7 @@ "ngx-flowchart": "https://github.com/thingsboard/ngx-flowchart.git#release/3.0.0", "ngx-hm-carousel": "^18.0.0", "ngx-markdown": "^18.1.0", - "ngx-sharebuttons": "^15.0.3", + "ngx-sharebuttons": "^15.0.6", "ngx-translate-messageformat-compiler": "^7.0.0", "objectpath": "^2.0.0", "prettier": "^2.8.3", @@ -112,60 +111,60 @@ }, "devDependencies": { "@angular-builders/custom-esbuild": "18.0.0", - "@angular-devkit/build-angular": "18.2.11", - "@angular-devkit/core": "18.2.11", - "@angular-devkit/schematics": "18.2.11", - "@angular-eslint/builder": "18.4.0", - "@angular-eslint/eslint-plugin": "18.4.0", - "@angular-eslint/eslint-plugin-template": "18.4.0", - "@angular-eslint/schematics": "18.4.0", - "@angular-eslint/template-parser": "18.4.0", - "@angular/build": "18.2.11", - "@angular/cli": "18.2.11", - "@angular/compiler-cli": "18.2.10", - "@angular/language-service": "18.2.10", + "@angular-devkit/build-angular": "18.2.12", + "@angular-devkit/core": "18.2.12", + "@angular-devkit/schematics": "18.2.12", + "@angular-eslint/builder": "18.4.2", + "@angular-eslint/eslint-plugin": "18.4.2", + "@angular-eslint/eslint-plugin-template": "18.4.2", + "@angular-eslint/schematics": "18.4.2", + "@angular-eslint/template-parser": "18.4.2", + "@angular/build": "18.2.12", + "@angular/cli": "18.2.12", + "@angular/compiler-cli": "18.2.13", + "@angular/language-service": "18.2.13", "@types/ace-diff": "^2.1.4", "@types/canvas-gauges": "^2.1.8", "@types/flot": "^0.0.36", "@types/flowjs": "^2.13.14", - "@types/jquery": "^3.5.31", + "@types/jquery": "^3.5.32", "@types/js-beautify": "^1.14.3", - "@types/leaflet": "1.9.12", + "@types/leaflet": "1.9.14", "@types/leaflet-polylinedecorator": "1.6.4", "@types/leaflet-providers": "1.2.4", "@types/leaflet.gridlayer.googlemutant": "0.4.9", - "@types/leaflet.markercluster": "1.5.4", - "@types/lodash": "^4.17.10", - "@types/node": "~18.15.11", + "@types/leaflet.markercluster": "1.5.5", + "@types/lodash": "^4.17.13", + "@types/node": "~20.17.8", "@types/raphael": "^2.3.9", "@types/react": "18.3.10", "@types/react-dom": "18.3.0", "@types/systemjs": "6.15.1", "@types/tinycolor2": "^1.4.6", "@types/tooltipster": "^0.0.35", - "@typescript-eslint/eslint-plugin": "^8.12.2", - "@typescript-eslint/parser": "^8.12.2", - "@typescript-eslint/types": "^8.12.2", - "@typescript-eslint/utils": "^8.12.2", + "@typescript-eslint/eslint-plugin": "^8.16.0", + "@typescript-eslint/parser": "^8.16.0", + "@typescript-eslint/types": "^8.16.0", + "@typescript-eslint/utils": "^8.16.0", "autoprefixer": "^10.4.20", "directory-tree": "^3.5.2", - "eslint": "~9.13.0", + "eslint": "~9.15.0", "eslint-plugin-import": "latest", - "eslint-plugin-jsdoc": "^50.4.3", + "eslint-plugin-jsdoc": "^50.6.0", "eslint-plugin-prefer-arrow": "latest", "eslint-plugin-tailwindcss": "^3.17.5", "ngrx-store-freeze": "^0.2.4", "patch-package": "^8.0.0", - "postcss": "^8.4.47", + "postcss": "^8.4.49", "postinstall-prepare": "^2.0.0", - "tailwindcss": "^3.4.14", + "tailwindcss": "^3.4.15", "ts-node": "^10.9.2", "typescript": "~5.5.4" }, "resolutions": { "@types/react": "18.3.10", "rc-virtual-list": "3.5.2", - "ace-builds": "1.36.2", + "ace-builds": "1.36.5", "tinymce": "6.8.5", "rollup": "4.22.4", "@babel/core": "7.25.2", diff --git a/ui-ngx/patches/@angular+build+18.2.11.patch b/ui-ngx/patches/@angular+build+18.2.12.patch similarity index 96% rename from ui-ngx/patches/@angular+build+18.2.11.patch rename to ui-ngx/patches/@angular+build+18.2.12.patch index 17b84e45c6..8861374691 100644 --- a/ui-ngx/patches/@angular+build+18.2.11.patch +++ b/ui-ngx/patches/@angular+build+18.2.12.patch @@ -12,7 +12,7 @@ index 625c621..4fc8bd8 100755 } async diagnoseFiles(modes = DiagnosticModes.All) { diff --git a/node_modules/@angular/build/src/tools/esbuild/angular/compiler-plugin.js b/node_modules/@angular/build/src/tools/esbuild/angular/compiler-plugin.js -index b1bb6ea..c76b4c6 100755 +index b03875c..8c48101 100755 --- a/node_modules/@angular/build/src/tools/esbuild/angular/compiler-plugin.js +++ b/node_modules/@angular/build/src/tools/esbuild/angular/compiler-plugin.js @@ -79,7 +79,7 @@ function createCompilerPlugin(pluginOptions, styleOptions) { @@ -24,7 +24,7 @@ index b1bb6ea..c76b4c6 100755 }, environment_options_1.maxWorkers, cacheStore?.createCache('jstransformer')); // Setup defines based on the values used by the Angular compiler-cli build.initialOptions.define ??= {}; -@@ -377,12 +377,14 @@ function createCompilerPlugin(pluginOptions, styleOptions) { +@@ -385,12 +385,14 @@ function createCompilerPlugin(pluginOptions, styleOptions) { async function hasSideEffects(path) { if (!pluginOptions.advancedOptimizations) { return undefined; diff --git a/ui-ngx/patches/@angular+core+18.2.10.patch b/ui-ngx/patches/@angular+core+18.2.13.patch similarity index 97% rename from ui-ngx/patches/@angular+core+18.2.10.patch rename to ui-ngx/patches/@angular+core+18.2.13.patch index 7779ea145b..c6d635e621 100644 --- a/ui-ngx/patches/@angular+core+18.2.10.patch +++ b/ui-ngx/patches/@angular+core+18.2.13.patch @@ -1,5 +1,5 @@ diff --git a/node_modules/@angular/core/fesm2022/core.mjs b/node_modules/@angular/core/fesm2022/core.mjs -index 6fe7ad6..291e4ed 100755 +index 7d9d345..b4d21c6 100755 --- a/node_modules/@angular/core/fesm2022/core.mjs +++ b/node_modules/@angular/core/fesm2022/core.mjs @@ -12859,13 +12859,13 @@ function findDirectiveDefMatches(tView, tNode) { diff --git a/ui-ngx/patches/jquery.terminal+2.43.1.patch b/ui-ngx/patches/jquery.terminal+2.44.1.patch similarity index 76% rename from ui-ngx/patches/jquery.terminal+2.43.1.patch rename to ui-ngx/patches/jquery.terminal+2.44.1.patch index f5c34ac212..640316d87c 100644 --- a/ui-ngx/patches/jquery.terminal+2.43.1.patch +++ b/ui-ngx/patches/jquery.terminal+2.44.1.patch @@ -1,14 +1,27 @@ +diff --git a/node_modules/jquery.terminal/css/jquery.terminal.css b/node_modules/jquery.terminal/css/jquery.terminal.css +index 996fe49..2e71336 100644 +--- a/node_modules/jquery.terminal/css/jquery.terminal.css ++++ b/node_modules/jquery.terminal/css/jquery.terminal.css +@@ -23,7 +23,7 @@ + } + .cmd .cmd-clipboard { + position: absolute !important; +- let: -16px; ++ letf: -16px; + left: calc(-16px / var(--pixel-density, 1)) !important; + top: 0 !important; + width: 16px; diff --git a/node_modules/jquery.terminal/css/jquery.terminal.min.css b/node_modules/jquery.terminal/css/jquery.terminal.min.css -index 830ba6e..978e57b 100644 +index c24c7ea..bf55845 100644 --- a/node_modules/jquery.terminal/css/jquery.terminal.min.css +++ b/node_modules/jquery.terminal/css/jquery.terminal.min.css @@ -13,7 +13,7 @@ * Released under the MIT license * - * Date: Sun, 08 Sep 2024 09:58:43 +0000 + * Date: Tue, 08 Oct 2024 20:29:54 +0000 - */.cmd .format,.cmd-prompt,.cmd-prompt div,.terminal .terminal-output .format{display:inline-block}.cmd,.terminal h1,.terminal h2,.terminal h3,.terminal h4,.terminal h5,.terminal h6,.terminal pre{margin:0}.cmd .cmd-clipboard{let:-16px;background:transparent!important;border:none!important;box-shadow:none!important;color:transparent!important;height:16px;height:calc(16px/var(--pixel-density, 1))!important;left:calc(-16px/var(--pixel-density, 1))!important;outline:none!important;overflow:hidden!important;padding:0!important;position:absolute!important;resize:none!important;text-indent:-9999em!important;top:0!important;top:calc(var(--cursor-line, 0)*var(--size, 1)*(14px/var(--pixel-density, 1)))!important;white-space:pre!important;width:16px;width:calc(16px/var(--pixel-density, 1))!important;z-index:1000!important}.visually-hidden{clip:rect(0 0 0 0);clip-path:inset(50%);height:1px;left:0;overflow:hidden;position:absolute;top:0;user-select:none;white-space:nowrap;width:1px}.cmd .cursor+span:empty,.cmd div.cmd-end-line span[data-text]:last-child,.cmd div.cmd-end-line span[data-text]:last-child span,.cmd span.cmd-end-line,.cmd span.cmd-end-line span,.cmd textarea{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.terminal audio,.terminal canvas,.terminal img,.terminal object{cursor:default}.terminal{--base-background:var(--background,#000);--base-color:var(--color);contain:content;line-height:normal;position:relative}.terminal-scroller{scrollbar-gutter:stable;box-sizing:border-box;height:100%;overflow-x:hidden;overflow-y:auto;position:relative}terminal.terminal-temp{visibility:hidden}body.terminal{height:100%;min-height:100vh}html{height:100%}body.full-screen-terminal,body.terminal{height:100%;margin:0}body.full-screen-terminal .terminal{height:100%!important}body.full-screen-terminal{height:100%;height:var(--terminal-force-height,100dvh)}.terminal>div.terminal-fill{height:100%;min-height:100%}.terminal>.terminal-pixel{display:inline;height:1px;left:-2px;position:absolute;top:-2px;width:1px}.terminal>.terminal-font .terminal-resizer,.terminal>.terminal-resizer{border:none!important;bottom:0!important;height:100%!important;left:0!important;overflow:hidden!important;padding:0!important;pointer-events:none!important;position:absolute!important;right:0!important;top:0!important;width:100%!important;z-index:-1!important}.cmd{padding:0;position:relative;width:100%;z-index:300}.terminal .cmd,.terminal .cmd div{background:transparent!important}.terminal a[tabindex="1000"],.terminal a[tabindex="1000"]:active,.terminal a[tabindex="1000"]:focus{outline:none}.cmd.cmd.cmd .cmd-inverted,.cmd.cmd.cmd .inverted,.terminal .inverted,.terminal .terminal-inverted{background-color:#aaa!important;color:#000!important}.cmd a[href],.terminal .terminal-output>:not(.raw) a[href],.terminal.external a[href]{color:#37f;color:var(--link-color,#37f);cursor:pointer}.cmd a[href]:not(.terminal-inverted),.terminal .terminal-output>:not(.raw) a[href]:not(.terminal-inverted),.terminal.external a[href]:not(.terminal-inverted){--color:var(--link-color,#37f);text-shadow:0 0 calc(var(--glow)*5px) var(--color,#ccc)}.terminal .terminal-output>:not(.raw) a[href].terminal-inverted,.terminal.external a[href].terminal-inverted{background:var(--color,#ccc);text-shadow:0 0 calc(var(--glow)*5px) var(--background,#000)}.cmd a[href]:hover,.terminal .terminal-output>:not(.raw) a[href]:hover,.terminal.external a[href]:hover{background-color:#37f;background-color:var(--link-color,#37f)!important;color:#000;color:var(--base-background,#000)!important;text-decoration:none}.cmd a[href] span,.terminal .terminal-output>:not(.raw) a[href] span,.terminal.external a[href] span{--color:var(--link-color,#37f);color:#37f!important;color:var(--link-color,#37f)!important;text-decoration:underline}.cmd a[href]:hover span,.terminal .terminal-output>:not(.raw) a[href]:hover span,.terminal.external a[href]:hover span{background-color:#37f!important;background-color:var(--link-color,#37f)!important;color:#000!important;color:var(--background,#000)!important;text-decoration:none}.terminal a[href]{transition-duration:.2s;transition-property:color,background-color;transition-timing-function:ease-out}.cmd .cmd-cursor,.cmd .cmd-cursor-line img,.cmd .cmd-cursor-line>span{display:inline-block}.cmd .cmd-cursor .emoji,.cmd .cmd-cursor.cmd-blink .fa,.cmd .cmd-cursor.cmd-blink .far,.cmd .cmd-cursor.cmd-blink .fas,.cmd .cmd-cursor.cmd-blink a,.cmd .cmd-cursor.cmd-blink>span[data-text]:not(.emoji):not(.fa):not(.far):not(.fas) span{-webkit-animation:terminal-blink 1s linear infinite;-moz-animation:terminal-blink 1s linear infinite;-ms-animation:terminal-blink 1s linear infinite;animation:terminal-blink 1s linear infinite}.bar.cmd .cmd-inverted,.bar.terminal .inverted{box-shadow:-2px 0 0 -1px #aaa;box-shadow:-2px 0 0 -1px var(--original-color,#aaa)}.cmd .cmd-prompt,.terminal,.terminal .terminal-output>div>div{display:block;height:auto}.terminal .terminal-output>div:not(.raw) div{clear:both;white-space:nowrap}.cmd .cmd-prompt:empty,.cmd .cmd-prompt>a,.cmd .cmd-prompt>span{float:left}.cmd-prompt [data-text]>span,.cmd-wrapper>div{white-space:pre}.cmd [data-text] span,.terminal [data-text] span{display:inline-block}.terminal-ouput span[style*=width]{min-height:14px;min-height:calc(var(--size, 1)*(14px/var(--pixel-density, 1)))}.cmd div,.terminal .terminal-output>:not(.raw)>div,.terminal.external div{line-height:1em}.cmd .cmd-prompt span.fa:before,.cmd .cmd-prompt span.fab:before,.cmd .cmd-prompt span.fad:before,.cmd .cmd-prompt span.fal:before,.cmd .cmd-prompt span.far:before,.cmd .cmd-prompt span.fas{position:relative;top:2px}.cmd,.cmd span:not(.fas):not(.far):not(.fa),.terminal,.terminal-font-forcer,.terminal-output>:not(.raw),.terminal-output>:not(.raw) a,.terminal-output>:not(.raw) span:not(.fas):not(.far):not(.fa){font-family:monospace;font-family:var(--font,monospace)}.cmd,.terminal{font-size:12px}.cmd .cmd-line:before,.terminal-output>div:not(.raw) div:before{content:"\0200B";display:inline-block;width:0}.cmd .cmd-line{white-space:nowrap}.cmd span[data-text],.terminal span[data-text]{display:inline-block}.cmd span[data-text]:not(.cmd-inverted):not(.token):not(.emoji),.terminal,terminal-output>div:not(.raw) div>span:not(.token):not(.inverted):not(.terminal-inverted):not(.cmd-inverted):not(.terminal-error):not(.emoji){background-color:#000;color:#aaa}.cmd span[data-text] span,.terminal span[data-text] span{text-decoration:inherit}.terminal .ansi>div{line-height:13px!important;line-height:calc(var(--size, 1)*(13px/var(--pixel-density, 1)))!important}.cmd .cmd-prompt span,.cmd span.cmd-prompt{--background:transparent}.cmd .emoji,.terminal-output .emoji{background-repeat:no-repeat;background-size:contain;color:transparent;height:12px;height:calc(12px/var(--pixel-density, 1));position:relative}.cmd .fa span,.cmd .far span,.cmd .fas span,.terminal .terminal-output .fa span,.terminal .terminal-output .far span,.terminal .terminal-output .fas span{clip:rect(1px,1px,1px,1px);background:transparent!important;color:transparent!important;position:absolute}.cmd .emoji,.cmd .emoji span,.terminal-output .emoji,.terminal-output .emoji span{display:inline-block;width:2ch}.cmd,.terminal{box-sizing:border-box}.cmd .cmd-cursor span:not(.token):not(.inverted){background-color:inherit;color:inherit}.cmd .emoji.emoji.emoji.emoji,.cmd .emoji.emoji.emoji.emoji span{background-color:transparent;color:transparent}.cmd .cmd-cursor *{background-color:transparent}.cmd span[style*=width] span,.terminal span[style*=width] span{width:inherit}.cmd div{clear:both}.cmd .cmd-prompt+div{clear:right}terminal .terminal-output>div{margin-top:-1px}.terminal-output>div.raw>div *{word-wrap:break-word;overflow-wrap:break-word}.terminal .terminal-font{float:left;font-size:inherit;left:0;line-height:inherit;margin-bottom:1px;position:absolute;top:-100%}.cmd>span:not(.cmd-prompt){float:left}.cmd .cmd-prompt span.cmd-line{display:block;float:none}.terminal table{border-collapse:collapse}.terminal td{border:1px solid #aaa}.cmd span[data-text]:not(.emoji):not(.fa):not(.fas):not(.far) span{background-color:inherit;color:inherit}.cmd [role=presentation].cmd-cursor-line{cursor:text;position:relative;z-index:100}.cmd .cmd-prompt{position:relative;z-index:200}.cmd [role=presentation]:not(.cmd-cursor-line){overflow:hidden}.cmd{--original-color:var( + */.cmd .format,.cmd-prompt,.cmd-prompt div,.terminal .terminal-output .format{display:inline-block}.cmd,.terminal h1,.terminal h2,.terminal h3,.terminal h4,.terminal h5,.terminal h6,.terminal pre{margin:0}.cmd .cmd-clipboard{left:-16px;background:transparent!important;border:none!important;box-shadow:none!important;color:transparent!important;height:16px;height:calc(16px/var(--pixel-density, 1))!important;left:calc(-16px/var(--pixel-density, 1))!important;outline:none!important;overflow:hidden!important;padding:0!important;position:absolute!important;resize:none!important;text-indent:-9999em!important;top:0!important;top:calc(var(--cursor-line, 0)*var(--size, 1)*(14px/var(--pixel-density, 1)))!important;white-space:pre!important;width:16px;width:calc(16px/var(--pixel-density, 1))!important;z-index:1000!important}.visually-hidden{clip:rect(0 0 0 0);clip-path:inset(50%);height:1px;left:0;overflow:hidden;position:absolute;top:0;user-select:none;white-space:nowrap;width:1px}.cmd .cursor+span:empty,.cmd div.cmd-end-line span[data-text]:last-child,.cmd div.cmd-end-line span[data-text]:last-child span,.cmd span.cmd-end-line,.cmd span.cmd-end-line span,.cmd textarea{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.terminal audio,.terminal canvas,.terminal img,.terminal object{cursor:default}.terminal{--base-background:var(--background,#000);--base-color:var(--color);contain:content;line-height:normal;position:relative}.terminal-scroller{scrollbar-gutter:stable;box-sizing:border-box;height:100%;overflow-x:hidden;overflow-y:auto;position:relative}terminal.terminal-temp{visibility:hidden}body.terminal{height:100%;min-height:100vh}html{height:100%}body.full-screen-terminal,body.terminal{height:100%;margin:0}body.full-screen-terminal .terminal{height:100%!important}body.full-screen-terminal{height:100%;height:var(--terminal-force-height,100dvh)}.terminal>div.terminal-fill{height:100%;min-height:100%}.terminal>.terminal-pixel{display:inline;height:1px;left:-2px;position:absolute;top:-2px;width:1px}.terminal>.terminal-font .terminal-resizer,.terminal>.terminal-resizer{border:none!important;bottom:0!important;height:100%!important;left:0!important;overflow:hidden!important;padding:0!important;pointer-events:none!important;position:absolute!important;right:0!important;top:0!important;width:100%!important;z-index:-1!important}.cmd{padding:0;position:relative;width:100%;z-index:300}.terminal .cmd,.terminal .cmd div{background:transparent!important}.terminal a[tabindex="1000"],.terminal a[tabindex="1000"]:active,.terminal a[tabindex="1000"]:focus{outline:none}.cmd.cmd.cmd .cmd-inverted,.cmd.cmd.cmd .inverted,.terminal .inverted,.terminal .terminal-inverted{background-color:#aaa!important;color:#000!important}.cmd a[href],.terminal .terminal-output>:not(.raw) a[href],.terminal.external a[href]{color:#37f;color:var(--link-color,#37f);cursor:pointer}.cmd a[href]:not(.terminal-inverted),.terminal .terminal-output>:not(.raw) a[href]:not(.terminal-inverted),.terminal.external a[href]:not(.terminal-inverted){--color:var(--link-color,#37f);text-shadow:0 0 calc(var(--glow)*5px) var(--color,#ccc)}.terminal .terminal-output>:not(.raw) a[href].terminal-inverted,.terminal.external a[href].terminal-inverted{background:var(--color,#ccc);text-shadow:0 0 calc(var(--glow)*5px) var(--background,#000)}.cmd a[href]:hover,.terminal .terminal-output>:not(.raw) a[href]:hover,.terminal.external a[href]:hover{background-color:#37f;background-color:var(--link-color,#37f)!important;color:#000;color:var(--base-background,#000)!important;text-decoration:none}.cmd a[href] span,.terminal .terminal-output>:not(.raw) a[href] span,.terminal.external a[href] span{--color:var(--link-color,#37f);color:#37f!important;color:var(--link-color,#37f)!important;text-decoration:underline}.cmd a[href]:hover span,.terminal .terminal-output>:not(.raw) a[href]:hover span,.terminal.external a[href]:hover span{background-color:#37f!important;background-color:var(--link-color,#37f)!important;color:#000!important;color:var(--background,#000)!important;text-decoration:none}.terminal a[href]{transition-duration:.2s;transition-property:color,background-color;transition-timing-function:ease-out}.cmd .cmd-cursor,.cmd .cmd-cursor-line img,.cmd .cmd-cursor-line>span{display:inline-block}.cmd .cmd-cursor .emoji,.cmd .cmd-cursor.cmd-blink .fa,.cmd .cmd-cursor.cmd-blink .far,.cmd .cmd-cursor.cmd-blink .fas,.cmd .cmd-cursor.cmd-blink a,.cmd .cmd-cursor.cmd-blink>span[data-text]:not(.emoji):not(.fa):not(.far):not(.fas) span{-webkit-animation:terminal-blink 1s linear infinite;-moz-animation:terminal-blink 1s linear infinite;-ms-animation:terminal-blink 1s linear infinite;animation:terminal-blink 1s linear infinite}.bar.cmd .cmd-inverted,.bar.terminal .inverted{box-shadow:-2px 0 0 -1px #aaa;box-shadow:-2px 0 0 -1px var(--original-color,#aaa)}.cmd .cmd-prompt,.terminal,.terminal .terminal-output>div>div{display:block;height:auto}.terminal .terminal-output>div:not(.raw) div{clear:both;white-space:nowrap}.cmd .cmd-prompt:empty,.cmd .cmd-prompt>a,.cmd .cmd-prompt>span{float:left}.cmd-prompt [data-text]>span,.cmd-wrapper>div{white-space:pre}.cmd [data-text] span,.terminal [data-text] span{display:inline-block}.terminal-ouput span[style*=width]{min-height:14px;min-height:calc(var(--size, 1)*(14px/var(--pixel-density, 1)))}.cmd div,.terminal .terminal-output>:not(.raw)>div,.terminal.external div{line-height:1em}.cmd .cmd-prompt span.fa:before,.cmd .cmd-prompt span.fab:before,.cmd .cmd-prompt span.fad:before,.cmd .cmd-prompt span.fal:before,.cmd .cmd-prompt span.far:before,.cmd .cmd-prompt span.fas{position:relative;top:2px}.cmd,.cmd span:not(.fas):not(.far):not(.fa),.terminal,.terminal-font-forcer,.terminal-output>:not(.raw),.terminal-output>:not(.raw) a,.terminal-output>:not(.raw) span:not(.fas):not(.far):not(.fa){font-family:monospace;font-family:var(--font,monospace)}.cmd,.terminal{font-size:12px}.cmd .cmd-line:before,.terminal-output>div:not(.raw) div:before{content:"\0200B";display:inline-block;width:0}.cmd .cmd-line{white-space:nowrap}.cmd span[data-text],.terminal span[data-text]{display:inline-block}.cmd span[data-text]:not(.cmd-inverted):not(.token):not(.emoji),.terminal,terminal-output>div:not(.raw) div>span:not(.token):not(.inverted):not(.terminal-inverted):not(.cmd-inverted):not(.terminal-error):not(.emoji){background-color:#000;color:#aaa}.cmd span[data-text] span,.terminal span[data-text] span{text-decoration:inherit}.terminal .ansi>div{line-height:13px!important;line-height:calc(var(--size, 1)*(13px/var(--pixel-density, 1)))!important}.cmd .cmd-prompt span,.cmd span.cmd-prompt{--background:transparent}.cmd .emoji,.terminal-output .emoji{background-repeat:no-repeat;background-size:contain;color:transparent;height:12px;height:calc(12px/var(--pixel-density, 1));position:relative}.cmd .fa span,.cmd .far span,.cmd .fas span,.terminal .terminal-output .fa span,.terminal .terminal-output .far span,.terminal .terminal-output .fas span{clip:rect(1px,1px,1px,1px);background:transparent!important;color:transparent!important;position:absolute}.cmd .emoji,.cmd .emoji span,.terminal-output .emoji,.terminal-output .emoji span{display:inline-block;width:2ch}.cmd,.terminal{box-sizing:border-box}.cmd .cmd-cursor span:not(.token):not(.inverted){background-color:inherit;color:inherit}.cmd .emoji.emoji.emoji.emoji,.cmd .emoji.emoji.emoji.emoji span{background-color:transparent;color:transparent}.cmd .cmd-cursor *{background-color:transparent}.cmd span[style*=width] span,.terminal span[style*=width] span{width:inherit}.cmd div{clear:both}.cmd .cmd-prompt+div{clear:right}terminal .terminal-output>div{margin-top:-1px}.terminal-output>div.raw>div *{word-wrap:break-word;overflow-wrap:break-word}.terminal .terminal-font{float:left;font-size:inherit;left:0;line-height:inherit;margin-bottom:1px;position:absolute;top:-100%}.cmd>span:not(.cmd-prompt){float:left}.cmd .cmd-prompt span.cmd-line{display:block;float:none}.terminal table{border-collapse:collapse}.terminal td{border:1px solid #aaa}.cmd span[data-text]:not(.emoji):not(.fa):not(.fas):not(.far) span{background-color:inherit;color:inherit}.cmd [role=presentation].cmd-cursor-line{cursor:text;position:relative;z-index:100}.cmd .cmd-prompt{position:relative;z-index:200}.cmd [role=presentation]:not(.cmd-cursor-line){overflow:hidden}.cmd{--original-color:var( --color,#aaa);--original-background:var( - --background)}.cmd a[href]{--original-color:var(--link-color,#37f)}@-webkit-keyframes terminal-blink{0%,50%{background-color:#aaa;background-color:var(--original-color,#aaa);-webkit-box-shadow:0 0 calc(var(--glow)*3px) var(--color,#aaa);box-shadow:0 0 calc(var(--glow)*3px) var(--color,#aaa);color:#000;color:var(--original-background,#000)}50.1%,to{background-color:inherit;color:inherit;color:var(--original-color,#aaa)}}@-moz-keyframes terminal-blink{0%,50%{background-color:#aaa;background-color:var(--original-color,#aaa);-webkit-box-shadow:0 0 calc(var(--glow)*3px) var(--color,#aaa);box-shadow:0 0 calc(var(--glow)*3px) var(--color,#aaa);color:#000;color:var(--original-background,#000)}50.1%,to{background-color:inherit;color:inherit;color:var(--original-color,#aaa)}}@keyframes terminal-blink{0%,50%{background-color:#aaa;background-color:var(--original-color,#aaa);-webkit-box-shadow:0 0 calc(var(--glow)*3px) var(--color,#aaa);box-shadow:0 0 calc(var(--glow)*3px) var(--color,#aaa);color:#000;color:var(--original-background,#000)}50.1%,to{background-color:inherit;-webkit-box-shadow:none;box-shadow:none;color:inherit;color:var(--original-color,#aaa)}}@-webkit-keyframes terminal-glow{0%,50%{background-color:#aaa;background-color:var(--original-color,#aaa);box-shadow:0 0 3px #aaa;-webkit-box-shadow:0 0 3px var(--color,#aaa);box-shadow:0 0 3px var(--color,#aaa);color:#000;color:var(--original-background,#000)}50.1%,to{background-color:inherit;-webkit-box-shadow:none;box-shadow:none;color:inherit}}@-moz-keyframes terminal-glow{0%,50%{background-color:#aaa;background-color:var(--original-color,#aaa);box-shadow:0 0 3px #aaa;-moz-box-shadow:0 0 3px var(--color,#aaa);box-shadow:0 0 3px var(--color,#aaa);color:#000;color:var(--original-background,#000)}50.1%,to{background-color:inherit;-moz-box-shadow:none;box-shadow:none;color:inherit}}@keyframes terminal-glow{0%,50%{background-color:#aaa;background-color:var(--original-color,#aaa);box-shadow:0 0 3px #aaa;box-shadow:0 0 3px var(--color,#aaa);color:#000;color:var(--original-background,#000)}50.1%,to{background-color:inherit;box-shadow:none;color:inherit}}@-webkit-keyframes terminal-bar{0%,50%{box-shadow:-2px 0 0 -1px #aaa;box-shadow:calc(var(--line-thickness, 2)*-1px) 0 0 -1px var(--original-color,#aaa)}50.1%,to{box-shadow:none}}@-moz-keyframes terminal-bar{0%,50%{box-shadow:-2px 0 0 -1px #aaa;box-shadow:calc(var(--line-thickness, 2)*-1px) 0 0 -1px var(--original-color,#aaa)}50.1%,to{box-shadow:none}}@keyframes terminal-bar{0%,50%{box-shadow:-2px 0 0 -1px #aaa;box-shadow:calc(var(--line-thickness, 2)*-1px) 0 0 -1px var(--original-color,#aaa)}50.1%,to{box-shadow:none}}@-webkit-keyframes terminal-underline{0%,50%{box-shadow:0 2px 0 #aaa;box-shadow:0 calc(var(--line-thickness, 2)*1px) 0 var(--original-color,#aaa)}50.1%,to{box-shadow:none}}@-moz-keyframes terminal-underline{0%,50%{box-shadow:0 2px 0 #aaa;box-shadow:0 calc(var(--line-thickness, 2)*1px) 0 var(--original-color,#aaa)}50.1%,to{box-shadow:none}}@keyframes terminal-underline{0%,50%{box-shadow:0 2px 0 #aaa;box-shadow:0 calc(var(--line-thickness, 2)*1px) 0 var(--original-color,#aaa)}50.1%,to{box-shadow:none}}@-webkit-keyframes terminal-none{0%,to{background-color:#aaa;background-color:var(--original-color,#aaa);-webkit-box-shadow:0 0 calc(var(--glow)*3px) var(--color,#aaa);box-shadow:0 0 calc(var(--glow)*3px) var(--color,#aaa);color:#000;color:var(--original-background,#000)}}@-moz-keyframes terminal-none{0%,to{background-color:#aaa;background-color:var(--original-color,#aaa);-webkit-box-shadow:0 0 calc(var(--glow)*3px) var(--color,#aaa);box-shadow:0 0 calc(var(--glow)*3px) var(--color,#aaa);color:#000;color:var(--background,#000)}}@keyframes terminal-none{0%,to{background-color:#aaa;background-color:var(--original-color,#aaa);-webkit-box-shadow:0 0 calc(var(--glow)*3px) var(--color,#aaa);box-shadow:0 0 calc(var(--glow)*3px) var(--color,#aaa);color:#000;color:var(--original-background,#000)}}.underline-animation .cmd .cmd-cursor .emoji,.underline-animation .cmd .cmd-cursor.cmd-blink .fa,.underline-animation .cmd .cmd-cursor.cmd-blink .far,.underline-animation .cmd .cmd-cursor.cmd-blink .fas,.underline-animation .cmd .cmd-cursor.cmd-blink a,.underline-animation .cmd .cmd-cursor.cmd-blink>span[data-text]:not(.emoji):not(.fa):not(.far):not(.fas) span{-webkit-animation-name:terminal-underline;-moz-animation-name:terminal-underline;-ms-animation-name:terminal-underline;animation-name:terminal-underline}.glow-animation .cmd .cmd-cursor .emoji,.glow-animation .cmd .cmd-cursor.cmd-blink .fa,.glow-animation .cmd .cmd-cursor.cmd-blink .far,.glow-animation .cmd .cmd-cursor.cmd-blink .fas,.glow-animation .cmd .cmd-cursor.cmd-blink a,.glow-animation .cmd .cmd-cursor.cmd-blink>span[data-text]:not(.emoji):not(.fa):not(.far):not(.fas) span{-webkit-animation-name:terminal-glow;-moz-animation-name:terminal-glow;-ms-animation-name:terminal-glow;animation-name:terminal-glow}.bar-animation .cmd .cmd-cursor .emoji,.bar-animation .cmd .cmd-cursor.cmd-blink .fa,.bar-animation .cmd .cmd-cursor.cmd-blink .far,.bar-animation .cmd .cmd-cursor.cmd-blink .fas,.bar-animation .cmd .cmd-cursor.cmd-blink a,.bar-animation .cmd .cmd-cursor.cmd-blink>span[data-text]:not(.emoji):not(.fa):not(.far):not(.fas) span{-webkit-animation-name:terminal-bar;-moz-animation-name:terminal-bar;-ms-animation-name:terminal-bar;animation-name:terminal-bar}@supports (-ms-ime-align:auto){.cmd .cmd-clipboard{margin-left:-9999px}@keyframes terminal-blink{0%,50%{background-color:var(--original-color,#aaa);color:var(--background,#000)}50.1%,to{background-color:var(--background,#000);color:var(--original-color,#aaa)}}@keyframes terminal-bar{0%,50%{border-left-color:var(--color,#aaa)}50.1%,to{border-left-color:var(--background,#000)}}@keyframes terminal-underline{0%,50%{border-bottom-color:var(--color,#aaa);line-height:12px;line-height:calc(var(--size, 1)*(12px/var(--pixel-density, 1)))}50.1%,to{border-bottom-color:var(--background,#000);line-height:12px;line-height:calc(var(--size, 1)*(12px/var(--pixel-density, 1)))}}}@media (-ms-high-contrast:active),(-ms-high-contrast:none){.cmd .cmd-clipboard{margin-left:-9999px}.underline-animation .cursor.blink span span{margin-top:1px}@-ms-keyframes terminal-blink{0%,50%{background-color:#aaa;color:#000}50.1%,to{background-color:#000;color:#aaa}}}.cmd span[data-text]::-moz-selection,.cmd span[data-text]:not(.far):not(.fa):not(.fas) span::-moz-selection,.terminal .terminal-output .raw ::-moz-selection,.terminal .terminal-output .raw div::-moz-selection,.terminal .terminal-output div div a::-moz-selection,.terminal .terminal-output div div::-moz-selection,.terminal .terminal-output span[data-text]::-moz-selection,.terminal .terminal-output span[data-text]:not(.far):not(.fa):not(.fas) span::-moz-selection{background-color:#aaa;color:#000}.terminal .cmd{pointer-events:none}.terminal .cmd-prompt *{pointer-events:visible}.cmd-prompt>span:first-child{margin-left:var(--prompt-offset)}.terminal .cmd textarea,.terminal .cmd-cursor-line>span,.terminal .cmd-wrapper>div:not(.cmd-cursor-line){pointer-events:visible}.cmd span[data-text]:not(.far):not(.fa):not(.fas) span::selection,.terminal .terminal-output .raw ::selection,.terminal .terminal-output .raw div::selection,.terminal .terminal-output div div a::selection,.terminal .terminal-output div div::selection,.terminal .terminal-output span[data-text]::selection,.terminal .terminal-output span[data-text]:not(.far):not(.fa):not(.fas) span::selection,.terminal-revese{background-color:hsla(0,0%,67%,.99);color:#000}.cmd .emoji span::-moz-selection,.cmd .emoji::-moz-selection,.cmd textarea::-moz-selection,.terminal-output .emoji span::-moz-selection,.terminal-output .emoji::-moz-selection{background-color:transparent!important;color:transparent!important}.cmd .emoji span::selection,.cmd .emoji::selection,.cmd textarea::selection,.terminal-output .emoji span::selection,.terminal-output .emoji::selection{background-color:transparent!important;color:transparent!important}.terminal .terminal-output>:not(.raw) .terminal-error,.terminal .terminal-output>:not(.raw) .terminal-error *{color:red;color:var(--error-color,red)}.tilda{left:0;position:fixed;top:0;width:100%;z-index:1100}.ui-dialog-content .terminal{box-sizing:border-box;height:100%;width:100%}.ui-dialog .ui-dialog-content.dterm{padding:0}.clear{clear:both}.terminal .terminal-fill{border:none;box-sizing:border-box;height:100%;left:0;margin:1px 0 0;opacity:.01;pointer-events:none;position:absolute;top:-100%;width:100%}.cmd-editable,.terminal .terminal-fill,.terminal-scroller{padding:calc(1px*var(--padding, 10)/var(--pixel-density, 1))}.cmd-editable{padding-top:0}.terminal-scroller{padding-bottom:0}.terminal .terminal-output>:not(.raw) .terminal-error,.terminal .terminal-output>:not(.raw) .terminal-error *{--color:var(--error-color,red);text-shadow:0 0 calc(var(--glow)*5px) var(--color)}.terminal .terminal-output>:not(.raw) a[href]{text-shadow:0 0 calc(var(--glow)*5px) var(--color)}.terminal .cmd{margin-bottom:calc(1px*var(--padding, 10)/var(--pixel-density, 1));position:relative}.terminal .partial,.terminal .partial>div{display:inline-block}@supports (--css:variables){.cmd,.cmd div,.cmd span[data-text]:not(.cmd-inverted):not(.token):not(.emoji),.terminal,.terminal-output>:not(.raw) a,.terminal-output>:not(.raw) div,.terminal-output>:not(.raw) span[data-text]:not(.token):not(.inverted):not(.terminal-inverted):not(.cmd-inverted):not(.terminal-error):not(.emoji),.terminal.external [data-text]{background-color:var(--background,#000);color:var(--color,#aaa)}.terminal{--terminal-line:calc(var(--size, 1)*(16px/var(--pixel-density, 1)) + 1px/var(--pixel-density, 1));height:auto;height:calc(var(--terminal-line)*var(--rows) + var(--padding, 10)*2px)}.cmd div,.terminal .terminal-output>:not(.raw)>div,.terminal.external div{line-height:var(--terminal-line,1em)}.cmd [data-text],.terminal [data-text]{text-shadow:var(--text-shadow,0 0 calc(var(--glow)*5px) var(--color,#ccc))}.terminal span[style*="--length"]{display:inline-block;width:calc(var(--length, 1)*var(--char-width, 7.23438)*1px)}.cmd,.cmd div,.cmd span,.terminal,.terminal-output>:not(.raw) a,.terminal-output>:not(.raw) div,.terminal-output>:not(.raw) span{font-size:calc(var(--size, 1)*(12px/var(--pixel-density, 1)))}.cmd .emoji,.terminal-output .emoji{height:calc(var(--size, 1)*(12px/var(--pixel-density, 1)))}.cmd .clipboard{top:calc(var(--size, 1)*(14/var(--pixel-density, 1))*var(--cursor-line, 0)*1px)}.cmd span[style*="--background"],.terminal span[style*="--background"]{--base-background:var(--background)}.cmd.cmd.cmd .cmd-inverted,.cmd.cmd.cmd .inverted,.terminal .inverted>span,.terminal .terminal-inverted>span{background-color:var(--color,#aaa)!important;color:var(--base-background,#000)!important}.cmd .cmd-cursor.cmd-blink{background-color:var(--background,#000);color:var(--color,#aaa)}.cmd .cmd-cursor.cmd-blink .emoji,.cmd .cmd-cursor.cmd-blink .fa,.cmd .cmd-cursor.cmd-blink .far,.cmd .cmd-cursor.cmd-blink .fas,.cmd .cmd-cursor.cmd-blink a,.cmd .cmd-cursor.cmd-blink>span[data-text]:not(.emoji):not(.fa):not(.far):not(.fas) span{--original-background:inherit;-webkit-animation:var(--animation,terminal-blink) 1s infinite linear;-moz-animation:var(--animation,terminal-blink) 1s infinite linear;-ms-animation:var(--animation,terminal-blink) 1s infinite linear;animation:var(--animation,terminal-blink) 1s infinite linear}.cmd .cmd-cursor.cmd-blink .emoji span{background:transparent;color:transparent}.cmd span[data-text]:not(.far):not(.fa):not(.fas):not(.emoji) span::-moz-selection,.terminal .terminal-output .raw ::-moz-selection,.terminal .terminal-output .raw div::-moz-selection,.terminal .terminal-output div div a::-moz-selection,.terminal .terminal-output div div::-moz-selection,.terminal .terminal-output span[data-text]::-moz-selection,.terminal .terminal-output span[data-text]:not(.far):not(.fa):not(.fas):not(.emoji) span::-moz-selection{background-color:var(--color,#aaa);color:var(--background,#000)}.terminal .terminal-output div div a::-moz-selection{background-color:var(--link-color,rgba(15,96,255,.99))!important;color:var(--background,#000)!important}.terminal .terminal-output div div a:hover::-moz-selection{background-color:var(--link-color,rgba(2,50,144,.99))!important}.cmd span[data-text]:not(.far):not(.fa):not(.fas):not(.emoji) span::selection,.terminal .terminal-output .raw ::selection,.terminal .terminal-output .raw div::selection,.terminal .terminal-output div div a::selection,.terminal .terminal-output div div::selection,.terminal .terminal-output span[data-text]:not(.emoji)::selection,.terminal .terminal-output span[data-text]:not(.far):not(.fa):not(.fas):not(.emoji) span::selection{background-color:var(--color,hsla(0,0%,67%,.99))!important;color:var(--background,#000)!important}.terminal .terminal-output div div a::selection{background-color:var(--link-color,rgba(15,96,255,.99))!important;color:var(--background,#000)!important}.terminal .terminal-output div div a:hover::selection{background-color:var(--link-color,rgba(2,50,144,.99))!important}}@property --color{syntax:"";inherits:true;initial-value:hsla(0,0%,67%,.99)}@property --background{syntax:"";inherits:true;initial-value:#000}@property --link-color{syntax:"";inherits:true;initial-value:#37f}@property --size{syntax:"";inherits:true;initial-value:1}@property --padding{syntax:"";inherits:true;initial-value:10}@property --rows{syntax:"";inherits:true}@supports (-ms-ime-align:auto){.cmd span[data-text]::selection,.terminal .terminal-output div div a::selection,.terminal .terminal-output div div::selection,.terminal h1::selection,.terminal h2::selection,.terminal h3::selection,.terminal h4::selection,.terminal h5::selection,.terminal h6::selection,.terminal pre::selection,.terminal td::selection{background-color:hsla(0,0%,67%,.99);color:#000}}.cmd .style .token.string,.cmd .token.entity,.cmd .token.operator,.cmd .token.string,.cmd .token.token,.cmd .token.url,.cmd .token.variable,.terminal .style .token.string,.terminal .token.entity,.terminal .token.operator,.terminal .token.string,.terminal .token.token,.terminal .token.url,.terminal .token.variable{background-color:inherit}.cmd .cursor-wrapper ul{float:left;left:0;list-style:none;margin:0;padding:0;position:absolute;top:14px}.cmd .cursor-wrapper li{cursor:pointer;white-space:nowrap}.cmd .cursor-wrapper li:hover{background:#aaa;color:#000}.cursor-wrapper{position:relative}.terminal-output img{vertical-align:top}.cmd img{border:1px solid transparent;height:14px;height:calc(var(--size, 1)*(14px/var(--pixel-density, 1)))}.cmd-cursor img{border-color:#ccc;border-color:var(--color,#ccc)}.terminal-output svg.terminal-broken-image{height:calc(var(--size, 1)*(14px/var(--pixel-density, 1)))}.terminal-output svg.terminal-broken-image use{fill:var(--color,#ccc)}.terminal-error{--color:var(--error-color)}.terminal-glow{--animation:terminal-glow}.terminal-glow .cmd-prompt>span,.terminal-glow .terminal-output>div a[href],.terminal-glow .terminal-output>div span,.terminal-glow [data-text] span,.terminal-glow a[data-text],.terminal-glow span[data-text]{text-shadow:1px 1px 5px #ccc;text-shadow:1px 1px 5px var(--color,#ccc)}.terminal-scroll-marker{height:1px;height:calc(1px/var(--pixel-density, 1));margin-top:-1px;margin-top:calc(-1px/var(--pixel-density, 1));position:relative;z-index:100}.terminal-scroll-marker div{bottom:0;left:0;position:absolute;right:0;z-index:200}.terminal-less{overscroll-behavior-y:contain;touch-action:none}.terminal-mobile.terminal-less .terminal-wrapper{pointer-events:none}.cmd-editable,.terminal-mobile.terminal-less .terminal-output a{pointer-events:visible}.cmd-editable:before{content:attr(data-cmd-prompt);display:inline-block}.cmd-editable{background:transparent;bottom:0;color:transparent;left:0;opacity:.01;outline:0 solid transparent;position:absolute;right:0;top:calc(var(--cmd-top)*1px);z-index:500}.terminal-scroller::-webkit-scrollbar{background:var(--background,#000);height:calc(6px/var(--pixel-density, 1));width:calc(6px/var(--pixel-density, 1))}.terminal-scroller::-webkit-scrollbar-thumb,.terminal-scroller::-webkit-scrollbar-thumb:hover{background:var(--color,#aaa)}.terminal-scroller{scrollbar-color:#aaa #000;scrollbar-color:var(--color,#aaa) var(--background,#000);scrollbar-width:thin}.terminal-hidden{clip:rect(0,0,0,0)!important;border:0!important;height:1px!important;margin:-1px!important;overflow:hidden!important;padding:0!important;position:absolute!important;white-space:nowrap!important;width:1px!important}.terminal-wrapper{--color:var(--base-color);--background:var(--base-background)}.cmd .token{--original-color:var(--color)}.cmd .terminal-blink,.terminal .terminal-blink{animation:terminal-ansi-blink 1s steps(2,start) infinite;-webkit-animation:terminal-ansi-blink 1s steps(2,start) infinite}.terminal .cmd *,.terminal .terminal-output,.terminal .terminal-output *{--background:transparent}.terminal .cmd ::selection,.terminal .cmd::selection,.terminal .terminal-output ::selection,.terminal .terminal-output::selection{--background:var(--base-background)}@keyframes terminal-ansi-blink{to{color:var(--background)}}@-webkit-keyframes terminal-ansi-blink{to{color:var(--background)}}@media (prefers-reduced-motion){:root{--animation:terminal-none}} + --background)}.cmd a[href]{--original-color:var(--link-color,#37f)}@-webkit-keyframes terminal-blink{0%,50%{background-color:#aaa;background-color:var(--original-color,#aaa);-webkit-box-shadow:0 0 calc(var(--glow)*3px) var(--color,#aaa);box-shadow:0 0 calc(var(--glow)*3px) var(--color,#aaa);color:#000;color:var(--original-background,#000)}50.1%,to{background-color:inherit;color:inherit;color:var(--original-color,#aaa)}}@-moz-keyframes terminal-blink{0%,50%{background-color:#aaa;background-color:var(--original-color,#aaa);-webkit-box-shadow:0 0 calc(var(--glow)*3px) var(--color,#aaa);box-shadow:0 0 calc(var(--glow)*3px) var(--color,#aaa);color:#000;color:var(--original-background,#000)}50.1%,to{background-color:inherit;color:inherit;color:var(--original-color,#aaa)}}@keyframes terminal-blink{0%,50%{background-color:#aaa;background-color:var(--original-color,#aaa);-webkit-box-shadow:0 0 calc(var(--glow)*3px) var(--color,#aaa);box-shadow:0 0 calc(var(--glow)*3px) var(--color,#aaa);color:#000;color:var(--original-background,#000)}50.1%,to{background-color:inherit;-webkit-box-shadow:none;box-shadow:none;color:inherit;color:var(--original-color,#aaa)}}@-webkit-keyframes terminal-glow{0%,50%{background-color:#aaa;background-color:var(--original-color,#aaa);box-shadow:0 0 3px #aaa;-webkit-box-shadow:0 0 3px var(--color,#aaa);box-shadow:0 0 3px var(--color,#aaa);color:#000;color:var(--original-background,#000)}50.1%,to{background-color:inherit;-webkit-box-shadow:none;box-shadow:none;color:inherit}}@-moz-keyframes terminal-glow{0%,50%{background-color:#aaa;background-color:var(--original-color,#aaa);box-shadow:0 0 3px #aaa;-moz-box-shadow:0 0 3px var(--color,#aaa);box-shadow:0 0 3px var(--color,#aaa);color:#000;color:var(--original-background,#000)}50.1%,to{background-color:inherit;-moz-box-shadow:none;box-shadow:none;color:inherit}}@keyframes terminal-glow{0%,50%{background-color:#aaa;background-color:var(--original-color,#aaa);box-shadow:0 0 3px #aaa;box-shadow:0 0 3px var(--color,#aaa);color:#000;color:var(--original-background,#000)}50.1%,to{background-color:inherit;box-shadow:none;color:inherit}}@-webkit-keyframes terminal-bar{0%,50%{box-shadow:-2px 0 0 -1px #aaa;box-shadow:calc(var(--line-thickness, 2)*-1px) 0 0 -1px var(--original-color,#aaa)}50.1%,to{box-shadow:none}}@-moz-keyframes terminal-bar{0%,50%{box-shadow:-2px 0 0 -1px #aaa;box-shadow:calc(var(--line-thickness, 2)*-1px) 0 0 -1px var(--original-color,#aaa)}50.1%,to{box-shadow:none}}@keyframes terminal-bar{0%,50%{box-shadow:-2px 0 0 -1px #aaa;box-shadow:calc(var(--line-thickness, 2)*-1px) 0 0 -1px var(--original-color,#aaa)}50.1%,to{box-shadow:none}}@-webkit-keyframes terminal-underline{0%,50%{box-shadow:0 2px 0 #aaa;box-shadow:0 calc(var(--line-thickness, 2)*1px) 0 var(--original-color,#aaa)}50.1%,to{box-shadow:none}}@-moz-keyframes terminal-underline{0%,50%{box-shadow:0 2px 0 #aaa;box-shadow:0 calc(var(--line-thickness, 2)*1px) 0 var(--original-color,#aaa)}50.1%,to{box-shadow:none}}@keyframes terminal-underline{0%,50%{box-shadow:0 2px 0 #aaa;box-shadow:0 calc(var(--line-thickness, 2)*1px) 0 var(--original-color,#aaa)}50.1%,to{box-shadow:none}}@-webkit-keyframes terminal-none{0%,to{background-color:#aaa;background-color:var(--original-color,#aaa);-webkit-box-shadow:0 0 calc(var(--glow)*3px) var(--color,#aaa);box-shadow:0 0 calc(var(--glow)*3px) var(--color,#aaa);color:#000;color:var(--original-background,#000)}}@-moz-keyframes terminal-none{0%,to{background-color:#aaa;background-color:var(--original-color,#aaa);-webkit-box-shadow:0 0 calc(var(--glow)*3px) var(--color,#aaa);box-shadow:0 0 calc(var(--glow)*3px) var(--color,#aaa);color:#000;color:var(--background,#000)}}@keyframes terminal-none{0%,to{background-color:#aaa;background-color:var(--original-color,#aaa);-webkit-box-shadow:0 0 calc(var(--glow)*3px) var(--color,#aaa);box-shadow:0 0 calc(var(--glow)*3px) var(--color,#aaa);color:#000;color:var(--original-background,#000)}}.underline-animation .cmd .cmd-cursor .emoji,.underline-animation .cmd .cmd-cursor.cmd-blink .fa,.underline-animation .cmd .cmd-cursor.cmd-blink .far,.underline-animation .cmd .cmd-cursor.cmd-blink .fas,.underline-animation .cmd .cmd-cursor.cmd-blink a,.underline-animation .cmd .cmd-cursor.cmd-blink>span[data-text]:not(.emoji):not(.fa):not(.far):not(.fas) span{-webkit-animation-name:terminal-underline;-moz-animation-name:terminal-underline;-ms-animation-name:terminal-underline;animation-name:terminal-underline}.glow-animation .cmd .cmd-cursor .emoji,.glow-animation .cmd .cmd-cursor.cmd-blink .fa,.glow-animation .cmd .cmd-cursor.cmd-blink .far,.glow-animation .cmd .cmd-cursor.cmd-blink .fas,.glow-animation .cmd .cmd-cursor.cmd-blink a,.glow-animation .cmd .cmd-cursor.cmd-blink>span[data-text]:not(.emoji):not(.fa):not(.far):not(.fas) span{-webkit-animation-name:terminal-glow;-moz-animation-name:terminal-glow;-ms-animation-name:terminal-glow;animation-name:terminal-glow}.bar-animation .cmd .cmd-cursor .emoji,.bar-animation .cmd .cmd-cursor.cmd-blink .fa,.bar-animation .cmd .cmd-cursor.cmd-blink .far,.bar-animation .cmd .cmd-cursor.cmd-blink .fas,.bar-animation .cmd .cmd-cursor.cmd-blink a,.bar-animation .cmd .cmd-cursor.cmd-blink>span[data-text]:not(.emoji):not(.fa):not(.far):not(.fas) span{-webkit-animation-name:terminal-bar;-moz-animation-name:terminal-bar;-ms-animation-name:terminal-bar;animation-name:terminal-bar}@supports (-ms-ime-align:auto){.cmd .cmd-clipboard{margin-left:-9999px}@keyframes terminal-blink{0%,50%{background-color:var(--original-color,#aaa);color:var(--background,#000)}50.1%,to{background-color:var(--background,#000);color:var(--original-color,#aaa)}}@keyframes terminal-bar{0%,50%{border-left-color:var(--color,#aaa)}50.1%,to{border-left-color:var(--background,#000)}}@keyframes terminal-underline{0%,50%{border-bottom-color:var(--color,#aaa);line-height:12px;line-height:calc(var(--size, 1)*(12px/var(--pixel-density, 1)))}50.1%,to{border-bottom-color:var(--background,#000);line-height:12px;line-height:calc(var(--size, 1)*(12px/var(--pixel-density, 1)))}}}@media (-ms-high-contrast:active),(-ms-high-contrast:none){.cmd .cmd-clipboard{margin-left:-9999px}.underline-animation .cursor.blink span span{margin-top:1px}@-ms-keyframes terminal-blink{0%,50%{background-color:#aaa;color:#000}50.1%,to{background-color:#000;color:#aaa}}}.cmd span[data-text]::-moz-selection,.cmd span[data-text]:not(.far):not(.fa):not(.fas) span::-moz-selection,.terminal .terminal-output .raw ::-moz-selection,.terminal .terminal-output .raw div::-moz-selection,.terminal .terminal-output div div a::-moz-selection,.terminal .terminal-output div div::-moz-selection,.terminal .terminal-output span[data-text]::-moz-selection,.terminal .terminal-output span[data-text]:not(.far):not(.fa):not(.fas) span::-moz-selection{background-color:#aaa;color:#000}.terminal .cmd{pointer-events:none}.terminal .cmd-prompt *{pointer-events:visible}.cmd-prompt>span:first-child{margin-left:var(--prompt-offset)}.terminal .cmd textarea,.terminal .cmd-cursor-line>span,.terminal .cmd-wrapper>div:not(.cmd-cursor-line){pointer-events:visible}.cmd span[data-text]:not(.far):not(.fa):not(.fas) span::selection,.terminal .terminal-output .raw ::selection,.terminal .terminal-output .raw div::selection,.terminal .terminal-output div div a::selection,.terminal .terminal-output div div::selection,.terminal .terminal-output span[data-text]::selection,.terminal .terminal-output span[data-text]:not(.far):not(.fa):not(.fas) span::selection,.terminal-revese{background-color:hsla(0,0%,67%,.99);color:#000}.cmd .emoji span::-moz-selection,.cmd .emoji::-moz-selection,.cmd textarea::-moz-selection,.terminal-output .emoji span::-moz-selection,.terminal-output .emoji::-moz-selection{background-color:transparent!important;color:transparent!important}.cmd .emoji span::selection,.cmd .emoji::selection,.cmd textarea::selection,.terminal-output .emoji span::selection,.terminal-output .emoji::selection{background-color:transparent!important;color:transparent!important}.terminal .terminal-output>:not(.raw) .terminal-error,.terminal .terminal-output>:not(.raw) .terminal-error *{color:red;color:var(--error-color,red)}.tilda{left:0;position:fixed;top:0;width:100%;z-index:1100}.ui-dialog-content .terminal{box-sizing:border-box;height:100%;width:100%}.ui-dialog .ui-dialog-content.dterm{padding:0}.clear{clear:both}.terminal .terminal-fill{border:none;box-sizing:border-box;height:100%;left:0;margin:1px 0 0;opacity:.01;pointer-events:none;position:absolute;top:-100%;width:100%}.cmd-editable,.terminal .terminal-fill,.terminal-scroller{padding:calc(1px*var(--padding, 10)/var(--pixel-density, 1))}.cmd-editable{padding-top:0}.terminal-scroller{padding-bottom:0}.terminal .terminal-output>:not(.raw) .terminal-error,.terminal .terminal-output>:not(.raw) .terminal-error *{--color:var(--error-color,red);text-shadow:0 0 calc(var(--glow)*5px) var(--color)}.terminal .terminal-output>:not(.raw) a[href]{text-shadow:0 0 calc(var(--glow)*5px) var(--color)}.terminal .cmd{margin-bottom:calc(1px*var(--padding, 10)/var(--pixel-density, 1));position:relative}.terminal .partial,.terminal .partial>div{display:inline-block}@supports (--css:variables){.cmd,.cmd div,.cmd span[data-text]:not(.cmd-inverted):not(.token):not(.emoji),.terminal,.terminal-output>:not(.raw) a,.terminal-output>:not(.raw) div,.terminal-output>:not(.raw) span[data-text]:not(.token):not(.inverted):not(.terminal-inverted):not(.cmd-inverted):not(.terminal-error):not(.emoji),.terminal.external [data-text]{background-color:var(--background,#000);color:var(--color,#aaa)}.terminal{--terminal-line:calc(var(--size, 1)*(16px/var(--pixel-density, 1)) + 1px/var(--pixel-density, 1));height:auto;height:calc(var(--terminal-line)*var(--rows) + var(--padding, 10)*2px)}.cmd div,.terminal,.terminal .terminal-output>:not(.raw)>div,.terminal.external div{line-height:round(var(--terminal-line,1em))}.cmd [data-text],.terminal [data-text]{text-shadow:var(--text-shadow,0 0 calc(var(--glow)*5px) var(--color,#ccc))}.terminal span[style*="--length"]{display:inline-block;width:calc(var(--length, 1)*var(--char-width, 7.23438)*1px)}.cmd,.cmd div,.cmd span,.terminal,.terminal-output>:not(.raw) a,.terminal-output>:not(.raw) div,.terminal-output>:not(.raw) span{font-size:calc(var(--size, 1)*(12px/var(--pixel-density, 1)))}.cmd .emoji,.terminal-output .emoji{height:calc(var(--size, 1)*(12px/var(--pixel-density, 1)))}.cmd .clipboard{top:calc(var(--size, 1)*(14/var(--pixel-density, 1))*var(--cursor-line, 0)*1px)}.cmd span[style*="--background"],.terminal span[style*="--background"]{--base-background:var(--background)}.cmd.cmd.cmd .cmd-inverted,.cmd.cmd.cmd .inverted,.terminal .inverted>span,.terminal .terminal-inverted>span{background-color:var(--color,#aaa)!important;color:var(--base-background,#000)!important}.cmd .cmd-cursor.cmd-blink{background-color:var(--background,#000);color:var(--color,#aaa)}.cmd .cmd-cursor.cmd-blink .emoji,.cmd .cmd-cursor.cmd-blink .fa,.cmd .cmd-cursor.cmd-blink .far,.cmd .cmd-cursor.cmd-blink .fas,.cmd .cmd-cursor.cmd-blink a,.cmd .cmd-cursor.cmd-blink>span[data-text]:not(.emoji):not(.fa):not(.far):not(.fas) span{--original-background:inherit;-webkit-animation:var(--animation,terminal-blink) 1s infinite linear;-moz-animation:var(--animation,terminal-blink) 1s infinite linear;-ms-animation:var(--animation,terminal-blink) 1s infinite linear;animation:var(--animation,terminal-blink) 1s infinite linear}.cmd .cmd-cursor.cmd-blink .emoji span{background:transparent;color:transparent}.cmd span[data-text]:not(.far):not(.fa):not(.fas):not(.emoji) span::-moz-selection,.terminal .terminal-output .raw ::-moz-selection,.terminal .terminal-output .raw div::-moz-selection,.terminal .terminal-output div div a::-moz-selection,.terminal .terminal-output div div::-moz-selection,.terminal .terminal-output span[data-text]::-moz-selection,.terminal .terminal-output span[data-text]:not(.far):not(.fa):not(.fas):not(.emoji) span::-moz-selection{background-color:var(--color,#aaa);color:var(--background,#000)}.terminal .terminal-output div div a::-moz-selection{background-color:var(--link-color,rgba(15,96,255,.99))!important;color:var(--background,#000)!important}.terminal .terminal-output div div a:hover::-moz-selection{background-color:var(--link-color,rgba(2,50,144,.99))!important}.cmd span[data-text]:not(.far):not(.fa):not(.fas):not(.emoji) span::selection,.terminal .terminal-output .raw ::selection,.terminal .terminal-output .raw div::selection,.terminal .terminal-output div div a::selection,.terminal .terminal-output div div::selection,.terminal .terminal-output span[data-text]:not(.emoji)::selection,.terminal .terminal-output span[data-text]:not(.far):not(.fa):not(.fas):not(.emoji) span::selection{background-color:var(--color,hsla(0,0%,67%,.99))!important;color:var(--background,#000)!important}.terminal .terminal-output div div a::selection{background-color:var(--link-color,rgba(15,96,255,.99))!important;color:var(--background,#000)!important}.terminal .terminal-output div div a:hover::selection{background-color:var(--link-color,rgba(2,50,144,.99))!important}}@property --color{syntax:"";inherits:true;initial-value:hsla(0,0%,67%,.99)}@property --background{syntax:"";inherits:true;initial-value:#000}@property --link-color{syntax:"";inherits:true;initial-value:#37f}@property --size{syntax:"";inherits:true;initial-value:1}@property --padding{syntax:"";inherits:true;initial-value:10}@property --rows{syntax:" | auto";inherits:true;initial-value:auto}@supports (-ms-ime-align:auto){.cmd span[data-text]::selection,.terminal .terminal-output div div a::selection,.terminal .terminal-output div div::selection,.terminal h1::selection,.terminal h2::selection,.terminal h3::selection,.terminal h4::selection,.terminal h5::selection,.terminal h6::selection,.terminal pre::selection,.terminal td::selection{background-color:hsla(0,0%,67%,.99);color:#000}}.cmd .style .token.string,.cmd .token.entity,.cmd .token.operator,.cmd .token.string,.cmd .token.token,.cmd .token.url,.cmd .token.variable,.terminal .style .token.string,.terminal .token.entity,.terminal .token.operator,.terminal .token.string,.terminal .token.token,.terminal .token.url,.terminal .token.variable{background-color:inherit}.cmd .cursor-wrapper ul{float:left;left:0;list-style:none;margin:0;padding:0;position:absolute;top:14px}.cmd .cursor-wrapper li{cursor:pointer;white-space:nowrap}.cmd .cursor-wrapper li:hover{background:#aaa;color:#000}.cursor-wrapper{position:relative}.terminal-output img{vertical-align:top}.cmd img{border:1px solid transparent;height:14px;height:calc(var(--size, 1)*(14px/var(--pixel-density, 1)))}.cmd-cursor img{border-color:#ccc;border-color:var(--color,#ccc)}.terminal-output svg.terminal-broken-image{height:calc(var(--size, 1)*(14px/var(--pixel-density, 1)))}.terminal-output svg.terminal-broken-image use{fill:var(--color,#ccc)}.terminal-error{--color:var(--error-color)}.terminal-glow{--animation:terminal-glow}.terminal-glow .cmd-prompt>span,.terminal-glow .terminal-output>div a[href],.terminal-glow .terminal-output>div span,.terminal-glow [data-text] span,.terminal-glow a[data-text],.terminal-glow span[data-text]{text-shadow:1px 1px 5px #ccc;text-shadow:1px 1px 5px var(--color,#ccc)}.terminal-scroll-marker{height:1px;height:calc(1px/var(--pixel-density, 1));margin-top:-1px;margin-top:calc(-1px/var(--pixel-density, 1));position:relative;z-index:100}.terminal-scroll-marker div{bottom:0;left:0;position:absolute;right:0;z-index:200}.terminal-less{overscroll-behavior-y:contain;touch-action:none}.terminal-mobile.terminal-less .terminal-wrapper{pointer-events:none}.cmd-editable,.terminal-mobile.terminal-less .terminal-output a{pointer-events:visible}.cmd-editable:before{content:attr(data-cmd-prompt);display:inline-block}.cmd-editable{background:transparent;bottom:0;color:transparent;left:0;opacity:.01;outline:0 solid transparent;position:absolute;right:0;top:calc(var(--cmd-top)*1px);z-index:500}.terminal-scroller::-webkit-scrollbar{background:var(--background,#000);height:calc(6px/var(--pixel-density, 1));width:calc(6px/var(--pixel-density, 1))}.terminal-scroller::-webkit-scrollbar-thumb,.terminal-scroller::-webkit-scrollbar-thumb:hover{background:var(--color,#aaa)}.terminal-scroller{scrollbar-color:#aaa #000;scrollbar-color:var(--color,#aaa) var(--background,#000);scrollbar-width:thin}.terminal-hidden{clip:rect(0,0,0,0)!important;border:0!important;height:1px!important;margin:-1px!important;overflow:hidden!important;padding:0!important;position:absolute!important;white-space:nowrap!important;width:1px!important}.terminal-wrapper{--color:var(--base-color);--background:var(--base-background)}.cmd .token{--original-color:var(--color)}.cmd .terminal-blink,.terminal .terminal-blink{animation:terminal-ansi-blink 1s steps(2,start) infinite;-webkit-animation:terminal-ansi-blink 1s steps(2,start) infinite}.terminal .cmd *,.terminal .terminal-output,.terminal .terminal-output *{--background:transparent}.terminal .cmd ::selection,.terminal .cmd::selection,.terminal .terminal-output ::selection,.terminal .terminal-output::selection{--background:var(--base-background)}@keyframes terminal-ansi-blink{to{color:var(--background)}}@-webkit-keyframes terminal-ansi-blink{to{color:var(--background)}}@media (prefers-reduced-motion){:root{--animation:terminal-none}} /*# sourceMappingURL=jquery.terminal.min.css.map */ \ No newline at end of file diff --git a/ui-ngx/src/app/modules/home/components/alias/entity-alias-select.component.html b/ui-ngx/src/app/modules/home/components/alias/entity-alias-select.component.html index 7ab346a075..008d1aa9fa 100644 --- a/ui-ngx/src/app/modules/home/components/alias/entity-alias-select.component.html +++ b/ui-ngx/src/app/modules/home/components/alias/entity-alias-select.component.html @@ -31,6 +31,14 @@ (click)="clear()"> close +