Browse Source

Merge branch 'master' into feature/single-switch

pull/10053/head
Igor Kulikov 3 years ago
parent
commit
08f2a23555
  1. 12
      application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
  2. 2
      application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
  3. 3
      application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java
  4. 47
      application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java
  5. 12
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java
  6. 4
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/alarm/AlarmMsgConstructor.java
  7. 2
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/alarm/AlarmMsgConstructorV1.java
  8. 2
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/alarm/AlarmMsgConstructorV2.java
  9. 29
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/alarm/BaseAlarmMsgConstructor.java
  10. 49
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java
  11. 125
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessor.java
  12. 5
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmProcessor.java
  13. 43
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java
  14. 38
      application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java
  15. 1
      application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmCommentService.java
  16. 7
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EntitiesLimitTriggerProcessor.java
  17. 4
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
  18. 32
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java
  19. 90
      application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java
  20. 17
      application/src/main/java/org/thingsboard/server/service/queue/ruleengine/TbQueueConsumerManagerTask.java
  21. 16
      application/src/main/java/org/thingsboard/server/service/queue/ruleengine/TbRuleEngineQueueConsumerManager.java
  22. 18
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java
  23. 18
      application/src/main/resources/thingsboard.yml
  24. 74
      application/src/test/java/org/thingsboard/server/actors/tenant/TenantActorTest.java
  25. 12
      application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java
  26. 6
      application/src/test/java/org/thingsboard/server/controller/AlarmControllerTest.java
  27. 26
      application/src/test/java/org/thingsboard/server/controller/ImageControllerTest.java
  28. 53
      application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java
  29. 128
      application/src/test/java/org/thingsboard/server/edge/AlarmEdgeTest.java
  30. 6
      application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java
  31. 4
      application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessorTest.java
  32. 4
      application/src/test/java/org/thingsboard/server/service/queue/ruleengine/TbRuleEngineQueueConsumerManagerTest.java
  33. 2
      application/src/test/resources/logback-test.xml
  34. 1
      common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmCommentService.java
  35. 13
      common/data/src/main/java/org/thingsboard/server/common/data/EdgeUtils.java
  36. 57
      common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventActionType.java
  37. 1
      common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventType.java
  38. 5
      common/data/src/main/java/org/thingsboard/server/common/data/page/BasePageDataIterable.java
  39. 7
      common/edge-api/src/main/proto/edge.proto
  40. 32
      common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java
  41. 4
      common/queue/src/main/java/org/thingsboard/server/queue/discovery/PartitionService.java
  42. 23
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java
  43. 4
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java
  44. 8
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java
  45. 11
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TransportActivityManager.java
  46. 53
      common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/service/TransportActivityManagerTest.java
  47. 21
      dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmCommentService.java
  48. 2
      dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
  49. 2
      dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java
  50. 4
      dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
  51. 2
      dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java
  52. 2
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java
  53. 2
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java
  54. 3
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
  55. 2
      dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java
  56. 2
      dao/src/main/java/org/thingsboard/server/dao/eventsourcing/SaveEntityEvent.java
  57. 4
      dao/src/main/java/org/thingsboard/server/dao/ota/BaseOtaPackageService.java
  58. 2
      dao/src/main/java/org/thingsboard/server/dao/queue/BaseQueueService.java
  59. 7
      dao/src/main/java/org/thingsboard/server/dao/resource/BaseImageService.java
  60. 2
      dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java
  61. 2
      dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java
  62. 2
      dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java
  63. 2
      dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java
  64. 2
      dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java
  65. 4
      dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeServiceImpl.java
  66. 2
      dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleServiceImpl.java
  67. 13
      dao/src/test/java/org/thingsboard/server/dao/service/AssetServiceTest.java
  68. 4
      monitoring/src/main/java/org/thingsboard/monitoring/config/MonitoringTarget.java
  69. 7
      monitoring/src/main/java/org/thingsboard/monitoring/config/transport/TransportInfo.java
  70. 6
      monitoring/src/main/java/org/thingsboard/monitoring/config/transport/TransportMonitoringTarget.java
  71. 9
      monitoring/src/main/java/org/thingsboard/monitoring/config/transport/TransportType.java
  72. 2
      monitoring/src/main/java/org/thingsboard/monitoring/data/notification/HighLatencyNotification.java
  73. 2
      monitoring/src/main/java/org/thingsboard/monitoring/data/notification/ServiceFailureNotification.java
  74. 2
      monitoring/src/main/java/org/thingsboard/monitoring/data/notification/ServiceRecoveryNotification.java
  75. 18
      monitoring/src/main/java/org/thingsboard/monitoring/notification/NotificationService.java
  76. 4
      monitoring/src/main/java/org/thingsboard/monitoring/notification/channels/NotificationChannel.java
  77. 11
      monitoring/src/main/java/org/thingsboard/monitoring/notification/channels/impl/SlackNotificationChannel.java
  78. 96
      monitoring/src/main/java/org/thingsboard/monitoring/service/transport/TransportHealthChecker.java
  79. 13
      monitoring/src/main/resources/tb-monitoring.yml
  80. 124
      rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java
  81. 6
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java
  82. 4
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java
  83. 5
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAlarmResult.java
  84. 13
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/AbstractTbMsgPushNode.java
  85. 10
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToCloudNode.java
  86. 36
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNode.java
  87. 1
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java
  88. 11
      transport/coap/src/main/resources/tb-coap-transport.yml
  89. 11
      transport/http/src/main/resources/tb-http-transport.yml
  90. 11
      transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml
  91. 11
      transport/mqtt/src/main/resources/tb-mqtt-transport.yml
  92. 11
      transport/snmp/src/main/resources/tb-snmp-transport.yml
  93. 2
      ui-ngx/src/app/modules/home/components/widget/config/widget-settings.component.ts

12
application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java

@ -76,6 +76,7 @@ import org.thingsboard.server.dao.alarm.AlarmCommentService;
import org.thingsboard.server.dao.asset.AssetProfileService; import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.audit.AuditLogService;
import org.thingsboard.server.dao.cassandra.CassandraCluster; import org.thingsboard.server.dao.cassandra.CassandraCluster;
import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.dashboard.DashboardService;
@ -86,6 +87,7 @@ import org.thingsboard.server.dao.edge.EdgeEventService;
import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.dao.nosql.CassandraStatementTask; import org.thingsboard.server.dao.nosql.CassandraStatementTask;
import org.thingsboard.server.dao.nosql.TbResultSetFuture; import org.thingsboard.server.dao.nosql.TbResultSetFuture;
import org.thingsboard.server.dao.notification.NotificationRequestService; import org.thingsboard.server.dao.notification.NotificationRequestService;
@ -907,6 +909,16 @@ class DefaultTbContext implements TbContext {
return mainCtx.getEntityService(); return mainCtx.getEntityService();
} }
@Override
public EventService getEventService() {
return mainCtx.getEventService();
}
@Override
public AuditLogService getAuditLogService() {
return mainCtx.getAuditLogService();
}
private TbMsgMetaData getActionMetaData(RuleNodeId ruleNodeId) { private TbMsgMetaData getActionMetaData(RuleNodeId ruleNodeId) {
TbMsgMetaData metaData = new TbMsgMetaData(); TbMsgMetaData metaData = new TbMsgMetaData();
metaData.putValue("ruleNodeId", ruleNodeId.toString()); metaData.putValue("ruleNodeId", ruleNodeId.toString());

2
application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java

@ -261,7 +261,7 @@ public class TenantActor extends RuleChainManagerActor {
edgeRpcService.updateEdge(tenantId, edge); edgeRpcService.updateEdge(tenantId, edge);
} }
} }
if (msg.getEntityId().getEntityType() == EntityType.DEVICE && ComponentLifecycleEvent.DELETED == msg.getEvent()) { if (msg.getEntityId().getEntityType() == EntityType.DEVICE && ComponentLifecycleEvent.DELETED == msg.getEvent() && isMyPartition(msg.getEntityId())) {
DeviceId deviceId = (DeviceId) msg.getEntityId(); DeviceId deviceId = (DeviceId) msg.getEntityId();
onToDeviceActorMsg(new DeviceDeleteMsg(tenantId, deviceId), true); onToDeviceActorMsg(new DeviceDeleteMsg(tenantId, deviceId), true);
deletedDevices.add(deviceId); deletedDevices.add(deviceId);

3
application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java

@ -216,6 +216,9 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService {
case ALARM: case ALARM:
alarmProcessor.processAlarmNotification(tenantId, edgeNotificationMsg); alarmProcessor.processAlarmNotification(tenantId, edgeNotificationMsg);
break; break;
case ALARM_COMMENT:
alarmProcessor.processAlarmCommentNotification(tenantId, edgeNotificationMsg);
break;
case RELATION: case RELATION:
relationProcessor.processRelationNotification(tenantId, edgeNotificationMsg); relationProcessor.processRelationNotification(tenantId, edgeNotificationMsg);
break; break;

47
application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java

@ -23,10 +23,12 @@ import org.springframework.stereotype.Component;
import org.springframework.transaction.event.TransactionalEventListener; import org.springframework.transaction.event.TransactionalEventListener;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.OtaPackageInfo; import org.thingsboard.server.common.data.OtaPackageInfo;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmApiCallResult; import org.thingsboard.server.common.data.alarm.AlarmApiCallResult;
import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelation;
@ -43,8 +45,6 @@ import org.thingsboard.server.dao.user.UserServiceImpl;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import static org.thingsboard.server.service.entitiy.DefaultTbNotificationEntityService.edgeTypeByActionType;
/** /**
* This event listener does not support async event processing because relay on ThreadLocal * This event listener does not support async event processing because relay on ThreadLocal
* Another possible approach is to implement a special annotation and a bunch of classes similar to TransactionalApplicationListener * Another possible approach is to implement a special annotation and a bunch of classes similar to TransactionalApplicationListener
@ -79,9 +79,12 @@ public class EdgeEventSourcingListener {
return; return;
} }
log.trace("[{}] SaveEntityEvent called: {}", event.getTenantId(), event); log.trace("[{}] SaveEntityEvent called: {}", event.getTenantId(), event);
EdgeEventActionType action = Boolean.TRUE.equals(event.getAdded()) ? EdgeEventActionType.ADDED : EdgeEventActionType.UPDATED; boolean isCreated = Boolean.TRUE.equals(event.getCreated());
String body = getBodyMsgForEntityEvent(event.getEntity());
EdgeEventType type = getEdgeEventTypeForEntityEvent(event.getEntity());
EdgeEventActionType action = getActionForEntityEvent(event.getEntity(), isCreated);
tbClusterService.sendNotificationMsgToEdge(event.getTenantId(), null, event.getEntityId(), tbClusterService.sendNotificationMsgToEdge(event.getTenantId(), null, event.getEntityId(),
null, null, action, edgeSynchronizationManager.getEdgeId().get()); body, type, action, edgeSynchronizationManager.getEdgeId().get());
} catch (Exception e) { } catch (Exception e) {
log.error("[{}] failed to process SaveEntityEvent: {}", event.getTenantId(), event, e); log.error("[{}] failed to process SaveEntityEvent: {}", event.getTenantId(), event, e);
} }
@ -91,20 +94,29 @@ public class EdgeEventSourcingListener {
public void handleEvent(DeleteEntityEvent<?> event) { public void handleEvent(DeleteEntityEvent<?> event) {
try { try {
log.trace("[{}] DeleteEntityEvent called: {}", event.getTenantId(), event); log.trace("[{}] DeleteEntityEvent called: {}", event.getTenantId(), event);
EdgeEventType type = getEdgeEventTypeForEntityEvent(event.getEntity());
EdgeEventActionType actionType = getEdgeEventActionTypeForEntityEvent(event.getEntity());
tbClusterService.sendNotificationMsgToEdge(event.getTenantId(), null, event.getEntityId(), tbClusterService.sendNotificationMsgToEdge(event.getTenantId(), null, event.getEntityId(),
JacksonUtil.toString(event.getEntity()), null, EdgeEventActionType.DELETED, JacksonUtil.toString(event.getEntity()), type, actionType,
edgeSynchronizationManager.getEdgeId().get()); edgeSynchronizationManager.getEdgeId().get());
} catch (Exception e) { } catch (Exception e) {
log.error("[{}] failed to process DeleteEntityEvent: {}", event.getTenantId(), event, e); log.error("[{}] failed to process DeleteEntityEvent: {}", event.getTenantId(), event, e);
} }
} }
private EdgeEventActionType getEdgeEventActionTypeForEntityEvent(Object entity) {
if (entity instanceof AlarmComment) {
return EdgeEventActionType.DELETED_COMMENT;
}
return EdgeEventActionType.DELETED;
}
@TransactionalEventListener(fallbackExecution = true) @TransactionalEventListener(fallbackExecution = true)
public void handleEvent(ActionEntityEvent event) { public void handleEvent(ActionEntityEvent event) {
try { try {
log.trace("[{}] ActionEntityEvent called: {}", event.getTenantId(), event); log.trace("[{}] ActionEntityEvent called: {}", event.getTenantId(), event);
tbClusterService.sendNotificationMsgToEdge(event.getTenantId(), event.getEdgeId(), event.getEntityId(), tbClusterService.sendNotificationMsgToEdge(event.getTenantId(), event.getEdgeId(), event.getEntityId(),
event.getBody(), null, edgeTypeByActionType(event.getActionType()), event.getBody(), null, EdgeUtils.getEdgeEventActionTypeByActionType(event.getActionType()),
edgeSynchronizationManager.getEdgeId().get()); edgeSynchronizationManager.getEdgeId().get());
} catch (Exception e) { } catch (Exception e) {
log.error("[{}] failed to process ActionEntityEvent: {}", event.getTenantId(), event, e); log.error("[{}] failed to process ActionEntityEvent: {}", event.getTenantId(), event, e);
@ -125,7 +137,7 @@ public class EdgeEventSourcingListener {
} }
log.trace("[{}] RelationActionEvent called: {}", event.getTenantId(), event); log.trace("[{}] RelationActionEvent called: {}", event.getTenantId(), event);
tbClusterService.sendNotificationMsgToEdge(event.getTenantId(), null, null, tbClusterService.sendNotificationMsgToEdge(event.getTenantId(), null, null,
JacksonUtil.toString(relation), EdgeEventType.RELATION, edgeTypeByActionType(event.getActionType()), JacksonUtil.toString(relation), EdgeEventType.RELATION, EdgeUtils.getEdgeEventActionTypeByActionType(event.getActionType()),
edgeSynchronizationManager.getEdgeId().get()); edgeSynchronizationManager.getEdgeId().get());
} catch (Exception e) { } catch (Exception e) {
log.error("[{}] failed to process RelationActionEvent: {}", event.getTenantId(), event, e); log.error("[{}] failed to process RelationActionEvent: {}", event.getTenantId(), event, e);
@ -173,4 +185,25 @@ public class EdgeEventSourcingListener {
} }
} }
} }
private EdgeEventType getEdgeEventTypeForEntityEvent(Object entity) {
if (entity instanceof AlarmComment) {
return EdgeEventType.ALARM_COMMENT;
}
return null;
}
private String getBodyMsgForEntityEvent(Object entity) {
if (entity instanceof AlarmComment) {
return JacksonUtil.toString(entity);
}
return null;
}
private EdgeEventActionType getActionForEntityEvent(Object entity, boolean isCreated) {
if (entity instanceof AlarmComment) {
return isCreated ? EdgeEventActionType.ADDED_COMMENT : EdgeEventActionType.UPDATED_COMMENT;
}
return isCreated ? EdgeEventActionType.ADDED : EdgeEventActionType.UPDATED;
}
} }

12
application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java

@ -39,6 +39,7 @@ import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.SortOrder; import org.thingsboard.server.common.data.page.SortOrder;
import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.gen.edge.v1.AlarmCommentUpdateMsg;
import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg; import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg;
import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg; import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg; import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg;
@ -530,6 +531,9 @@ public final class EdgeGrpcSession implements Closeable {
case RPC_CALL: case RPC_CALL:
case ASSIGNED_TO_CUSTOMER: case ASSIGNED_TO_CUSTOMER:
case UNASSIGNED_FROM_CUSTOMER: case UNASSIGNED_FROM_CUSTOMER:
case ADDED_COMMENT:
case UPDATED_COMMENT:
case DELETED_COMMENT:
downlinkMsg = convertEntityEventToDownlink(edgeEvent); downlinkMsg = convertEntityEventToDownlink(edgeEvent);
log.trace("[{}][{}] entity message processed [{}]", this.tenantId, this.sessionId, downlinkMsg); log.trace("[{}][{}] entity message processed [{}]", this.tenantId, this.sessionId, downlinkMsg);
break; break;
@ -644,6 +648,8 @@ public final class EdgeGrpcSession implements Closeable {
return ctx.getRuleChainProcessor().convertRuleChainMetadataEventToDownlink(edgeEvent, this.edgeVersion); return ctx.getRuleChainProcessor().convertRuleChainMetadataEventToDownlink(edgeEvent, this.edgeVersion);
case ALARM: case ALARM:
return ctx.getAlarmProcessor().convertAlarmEventToDownlink(edgeEvent, this.edgeVersion); return ctx.getAlarmProcessor().convertAlarmEventToDownlink(edgeEvent, this.edgeVersion);
case ALARM_COMMENT:
return ctx.getAlarmProcessor().convertAlarmCommentEventToDownlink(edgeEvent, this.edgeVersion);
case USER: case USER:
return ctx.getUserProcessor().convertUserEventToDownlink(edgeEvent, this.edgeVersion); return ctx.getUserProcessor().convertUserEventToDownlink(edgeEvent, this.edgeVersion);
case RELATION: case RELATION:
@ -714,6 +720,12 @@ public final class EdgeGrpcSession implements Closeable {
.processAlarmMsgFromEdge(edge.getTenantId(), edge.getId(), alarmUpdateMsg)); .processAlarmMsgFromEdge(edge.getTenantId(), edge.getId(), alarmUpdateMsg));
} }
} }
if (uplinkMsg.getAlarmCommentUpdateMsgCount() > 0) {
for (AlarmCommentUpdateMsg alarmCommentUpdateMsg : uplinkMsg.getAlarmCommentUpdateMsgList()) {
result.add(((AlarmProcessor) ctx.getAlarmEdgeProcessorFactory().getProcessorByEdgeVersion(this.edgeVersion))
.processAlarmCommentMsgFromEdge(edge.getTenantId(), edge.getId(), alarmCommentUpdateMsg));
}
}
if (uplinkMsg.getEntityViewUpdateMsgCount() > 0) { if (uplinkMsg.getEntityViewUpdateMsgCount() > 0) {
for (EntityViewUpdateMsg entityViewUpdateMsg : uplinkMsg.getEntityViewUpdateMsgList()) { for (EntityViewUpdateMsg entityViewUpdateMsg : uplinkMsg.getEntityViewUpdateMsgList()) {
result.add(((EntityViewProcessor) ctx.getEntityViewProcessorFactory().getProcessorByEdgeVersion(this.edgeVersion)) result.add(((EntityViewProcessor) ctx.getEntityViewProcessorFactory().getProcessorByEdgeVersion(this.edgeVersion))

4
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/alarm/AlarmMsgConstructor.java

@ -16,6 +16,8 @@
package org.thingsboard.server.service.edge.rpc.constructor.alarm; package org.thingsboard.server.service.edge.rpc.constructor.alarm;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.gen.edge.v1.AlarmCommentUpdateMsg;
import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg; import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.service.edge.rpc.constructor.MsgConstructor; import org.thingsboard.server.service.edge.rpc.constructor.MsgConstructor;
@ -23,4 +25,6 @@ import org.thingsboard.server.service.edge.rpc.constructor.MsgConstructor;
public interface AlarmMsgConstructor extends MsgConstructor { public interface AlarmMsgConstructor extends MsgConstructor {
AlarmUpdateMsg constructAlarmUpdatedMsg(UpdateMsgType msgType, Alarm alarm, String entityName); AlarmUpdateMsg constructAlarmUpdatedMsg(UpdateMsgType msgType, Alarm alarm, String entityName);
AlarmCommentUpdateMsg constructAlarmCommentUpdatedMsg(UpdateMsgType msgType, AlarmComment alarmComment);
} }

2
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/alarm/AlarmMsgConstructorV1.java

@ -24,7 +24,7 @@ import org.thingsboard.server.queue.util.TbCoreComponent;
@Component @Component
@TbCoreComponent @TbCoreComponent
public class AlarmMsgConstructorV1 implements AlarmMsgConstructor { public class AlarmMsgConstructorV1 extends BaseAlarmMsgConstructor {
@Override @Override
public AlarmUpdateMsg constructAlarmUpdatedMsg(UpdateMsgType msgType, Alarm alarm, String entityName) { public AlarmUpdateMsg constructAlarmUpdatedMsg(UpdateMsgType msgType, Alarm alarm, String entityName) {

2
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/alarm/AlarmMsgConstructorV2.java

@ -24,7 +24,7 @@ import org.thingsboard.server.queue.util.TbCoreComponent;
@Component @Component
@TbCoreComponent @TbCoreComponent
public class AlarmMsgConstructorV2 implements AlarmMsgConstructor { public class AlarmMsgConstructorV2 extends BaseAlarmMsgConstructor {
@Override @Override
public AlarmUpdateMsg constructAlarmUpdatedMsg(UpdateMsgType msgType, Alarm alarm, String entityName) { public AlarmUpdateMsg constructAlarmUpdatedMsg(UpdateMsgType msgType, Alarm alarm, String entityName) {

29
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/alarm/BaseAlarmMsgConstructor.java

@ -0,0 +1,29 @@
/**
* 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.constructor.alarm;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.gen.edge.v1.AlarmCommentUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
public abstract class BaseAlarmMsgConstructor implements AlarmMsgConstructor {
@Override
public AlarmCommentUpdateMsg constructAlarmCommentUpdatedMsg(UpdateMsgType msgType, AlarmComment alarmComment) {
return AlarmCommentUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(alarmComment)).build();
}
}

49
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java

@ -51,6 +51,7 @@ import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.msg.TbMsgType;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageDataIterableByTenantIdEntityId;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.relation.RelationTypeGroup;
@ -59,6 +60,7 @@ import org.thingsboard.server.common.data.rule.RuleChainConnectionInfo;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData; 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.alarm.AlarmService;
import org.thingsboard.server.dao.asset.AssetProfileService; import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.asset.AssetService;
@ -145,6 +147,9 @@ public abstract class BaseEdgeProcessor {
@Autowired @Autowired
protected AlarmService alarmService; protected AlarmService alarmService;
@Autowired
protected AlarmCommentService alarmCommentService;
@Autowired @Autowired
protected DeviceService deviceService; protected DeviceService deviceService;
@ -350,10 +355,13 @@ public abstract class BaseEdgeProcessor {
case ALARM_ASSIGNED: case ALARM_ASSIGNED:
case ALARM_UNASSIGNED: case ALARM_UNASSIGNED:
case CREDENTIALS_REQUEST: case CREDENTIALS_REQUEST:
case ADDED_COMMENT:
case UPDATED_COMMENT:
return true; return true;
} }
switch (type) { switch (type) {
case ALARM: case ALARM:
case ALARM_COMMENT:
case RULE_CHAIN: case RULE_CHAIN:
case RULE_CHAIN_METADATA: case RULE_CHAIN_METADATA:
case USER: case USER:
@ -441,14 +449,17 @@ public abstract class BaseEdgeProcessor {
case CREDENTIALS_UPDATED: case CREDENTIALS_UPDATED:
case ASSIGNED_TO_CUSTOMER: case ASSIGNED_TO_CUSTOMER:
case UNASSIGNED_FROM_CUSTOMER: case UNASSIGNED_FROM_CUSTOMER:
case UPDATED_COMMENT:
return UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE; return UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE;
case ADDED: case ADDED:
case ASSIGNED_TO_EDGE: case ASSIGNED_TO_EDGE:
case RELATION_ADD_OR_UPDATE: case RELATION_ADD_OR_UPDATE:
case ADDED_COMMENT:
return UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE; return UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE;
case DELETED: case DELETED:
case UNASSIGNED_FROM_EDGE: case UNASSIGNED_FROM_EDGE:
case RELATION_DELETED: case RELATION_DELETED:
case DELETED_COMMENT:
return UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE; return UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE;
case ALARM_ACK: case ALARM_ACK:
return UpdateMsgType.ALARM_ACK_RPC_MESSAGE; return UpdateMsgType.ALARM_ACK_RPC_MESSAGE;
@ -517,22 +528,14 @@ public abstract class BaseEdgeProcessor {
private ListenableFuture<Void> processNotificationToRelatedEdges(TenantId tenantId, EntityId entityId, EdgeEventType type, private ListenableFuture<Void> processNotificationToRelatedEdges(TenantId tenantId, EntityId entityId, EdgeEventType type,
EdgeEventActionType actionType, EdgeId sourceEdgeId) { EdgeEventActionType actionType, EdgeId sourceEdgeId) {
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
PageData<EdgeId> pageData;
List<ListenableFuture<Void>> futures = new ArrayList<>(); List<ListenableFuture<Void>> futures = new ArrayList<>();
do { PageDataIterableByTenantIdEntityId<EdgeId> edgeIds =
pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, entityId, pageLink); new PageDataIterableByTenantIdEntityId<>(edgeService::findRelatedEdgeIdsByEntityId, tenantId, entityId, DEFAULT_PAGE_SIZE);
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { for (EdgeId relatedEdgeId : edgeIds) {
for (EdgeId relatedEdgeId : pageData.getData()) { if (!relatedEdgeId.equals(sourceEdgeId)) {
if (!relatedEdgeId.equals(sourceEdgeId)) { futures.add(saveEdgeEvent(tenantId, relatedEdgeId, type, actionType, entityId, null));
futures.add(saveEdgeEvent(tenantId, relatedEdgeId, type, actionType, entityId, null));
}
}
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
} }
} while (pageData != null && pageData.hasNext()); }
return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService); return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService);
} }
@ -715,19 +718,13 @@ public abstract class BaseEdgeProcessor {
} }
private boolean isEntityNotAssignedToEdge(TenantId tenantId, EntityId entityId, EdgeId edgeId) { private boolean isEntityNotAssignedToEdge(TenantId tenantId, EntityId entityId, EdgeId edgeId) {
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); PageDataIterableByTenantIdEntityId<EdgeId> edgeIds =
PageData<EdgeId> pageData; new PageDataIterableByTenantIdEntityId<>(edgeService::findRelatedEdgeIdsByEntityId, tenantId, entityId, DEFAULT_PAGE_SIZE);
do { for (EdgeId edgeId1 : edgeIds) {
pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, entityId, pageLink); if (edgeId1.equals(edgeId)) {
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { return false;
if (pageData.getData().contains(edgeId)) {
return false;
}
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
} }
} while (pageData != null && pageData.hasNext()); }
return true; return true;
} }
} }

125
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessor.java

@ -22,6 +22,7 @@ import lombok.extern.slf4j.Slf4j;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.edge.EdgeEventType;
@ -29,12 +30,14 @@ import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageDataIterableByTenantIdEntityId;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.gen.edge.v1.AlarmCommentUpdateMsg;
import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg; import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion; 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.gen.transport.TransportProtos;
import org.thingsboard.server.service.edge.rpc.constructor.alarm.AlarmMsgConstructor;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -67,58 +70,90 @@ public abstract class AlarmEdgeProcessor extends BaseAlarmProcessor implements A
return null; return null;
} }
@Override
public ListenableFuture<Void> processAlarmCommentMsgFromEdge(TenantId tenantId, EdgeId edgeId, AlarmCommentUpdateMsg alarmCommentUpdateMsg) {
log.trace("[{}] processAlarmCommentMsgFromEdge [{}]", tenantId, alarmCommentUpdateMsg);
try {
edgeSynchronizationManager.getEdgeId().set(edgeId);
return processAlarmCommentMsg(tenantId, alarmCommentUpdateMsg);
} finally {
edgeSynchronizationManager.getEdgeId().remove();
}
}
@Override
public DownlinkMsg convertAlarmCommentEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction());
switch (edgeEvent.getAction()) {
case ADDED_COMMENT:
case UPDATED_COMMENT:
case DELETED_COMMENT:
AlarmComment alarmComment = JacksonUtil.convertValue(edgeEvent.getBody(), AlarmComment.class);
if (alarmComment != null) {
return DownlinkMsg.newBuilder()
.setDownlinkMsgId(EdgeUtils.nextPositiveInt())
.addAlarmCommentUpdateMsg(((AlarmMsgConstructor) alarmMsgConstructorFactory
.getMsgConstructorByEdgeVersion(edgeVersion)).constructAlarmCommentUpdatedMsg(msgType, alarmComment))
.build();
}
default:
return null;
}
}
public ListenableFuture<Void> processAlarmNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { public ListenableFuture<Void> processAlarmNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) {
EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()); EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction());
AlarmId alarmId = new AlarmId(new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB())); AlarmId alarmId = new AlarmId(new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB()));
EdgeId originatorEdgeId = safeGetEdgeId(edgeNotificationMsg.getOriginatorEdgeIdMSB(), edgeNotificationMsg.getOriginatorEdgeIdLSB()); EdgeId originatorEdgeId = safeGetEdgeId(edgeNotificationMsg.getOriginatorEdgeIdMSB(), edgeNotificationMsg.getOriginatorEdgeIdLSB());
switch (actionType) { if (EdgeEventActionType.DELETED.equals(actionType)) {
case DELETED: Alarm deletedAlarm = JacksonUtil.fromString(edgeNotificationMsg.getBody(), Alarm.class);
Alarm deletedAlarm = JacksonUtil.fromString(edgeNotificationMsg.getBody(), Alarm.class); if (deletedAlarm == null) {
if (deletedAlarm == null) { return Futures.immediateFuture(null);
return Futures.immediateFuture(null); }
} List<ListenableFuture<Void>> delFutures = pushEventToAllRelatedEdges(tenantId, deletedAlarm.getOriginator(),
List<ListenableFuture<Void>> delFutures = pushEventToAllRelatedEdges(tenantId, deletedAlarm.getOriginator(), alarmId, actionType, JacksonUtil.valueToTree(deletedAlarm), originatorEdgeId, EdgeEventType.ALARM);
alarmId, actionType, JacksonUtil.valueToTree(deletedAlarm), originatorEdgeId); return Futures.transform(Futures.allAsList(delFutures), voids -> null, dbCallbackExecutorService);
return Futures.transform(Futures.allAsList(delFutures), voids -> null, dbCallbackExecutorService); }
default: ListenableFuture<Alarm> alarmFuture = alarmService.findAlarmByIdAsync(tenantId, alarmId);
ListenableFuture<Alarm> alarmFuture = alarmService.findAlarmByIdAsync(tenantId, alarmId); return Futures.transformAsync(alarmFuture, alarm -> {
return Futures.transformAsync(alarmFuture, alarm -> { if (alarm == null) {
if (alarm == null) { return Futures.immediateFuture(null);
return Futures.immediateFuture(null); }
} EdgeEventType type = EdgeUtils.getEdgeEventTypeByEntityType(alarm.getOriginator().getEntityType());
EdgeEventType type = EdgeUtils.getEdgeEventTypeByEntityType(alarm.getOriginator().getEntityType()); if (type == null) {
if (type == null) { return Futures.immediateFuture(null);
return Futures.immediateFuture(null); }
} List<ListenableFuture<Void>> futures = pushEventToAllRelatedEdges(tenantId, alarm.getOriginator(),
List<ListenableFuture<Void>> futures = pushEventToAllRelatedEdges(tenantId, alarm.getOriginator(), alarmId, actionType, null, originatorEdgeId, EdgeEventType.ALARM);
alarmId, actionType, null, originatorEdgeId); return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService);
return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService); }, dbCallbackExecutorService);
}, dbCallbackExecutorService); }
public ListenableFuture<Void> processAlarmCommentNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) {
EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction());
AlarmId alarmId = new AlarmId(new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB()));
EdgeId originatorEdgeId = safeGetEdgeId(edgeNotificationMsg.getOriginatorEdgeIdMSB(), edgeNotificationMsg.getOriginatorEdgeIdLSB());
AlarmComment alarmComment = JacksonUtil.fromString(edgeNotificationMsg.getBody(), AlarmComment.class);
if (alarmComment == null) {
return Futures.immediateFuture(null);
} }
Alarm alarmById = alarmService.findAlarmById(tenantId, new AlarmId(alarmComment.getAlarmId().getId()));
List<ListenableFuture<Void>> delFutures = pushEventToAllRelatedEdges(tenantId, alarmById.getOriginator(),
alarmId, actionType, JacksonUtil.valueToTree(alarmComment), originatorEdgeId, EdgeEventType.ALARM_COMMENT);
return Futures.transform(Futures.allAsList(delFutures), voids -> null, dbCallbackExecutorService);
} }
private List<ListenableFuture<Void>> pushEventToAllRelatedEdges(TenantId tenantId, EntityId originatorId, AlarmId alarmId, EdgeEventActionType actionType, JsonNode body, EdgeId sourceEdgeId) { private List<ListenableFuture<Void>> pushEventToAllRelatedEdges(TenantId tenantId, EntityId originatorId, AlarmId alarmId,
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); EdgeEventActionType actionType, JsonNode body, EdgeId sourceEdgeId,
PageData<EdgeId> pageData; EdgeEventType edgeEventType) {
List<ListenableFuture<Void>> futures = new ArrayList<>(); List<ListenableFuture<Void>> futures = new ArrayList<>();
do { PageDataIterableByTenantIdEntityId<EdgeId> edgeIds =
pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, originatorId, pageLink); new PageDataIterableByTenantIdEntityId<>(edgeService::findRelatedEdgeIdsByEntityId, tenantId, originatorId, DEFAULT_PAGE_SIZE);
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { for (EdgeId relatedEdgeId : edgeIds) {
for (EdgeId relatedEdgeId : pageData.getData()) { if (!relatedEdgeId.equals(sourceEdgeId)) {
if (!relatedEdgeId.equals(sourceEdgeId)) { futures.add(saveEdgeEvent(tenantId, relatedEdgeId, edgeEventType, actionType, alarmId, body));
futures.add(saveEdgeEvent(tenantId,
relatedEdgeId,
EdgeEventType.ALARM,
actionType,
alarmId,
body));
}
}
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
} }
} while (pageData != null && pageData.hasNext()); }
return futures; return futures;
} }
} }

5
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmProcessor.java

@ -19,6 +19,7 @@ import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.gen.edge.v1.AlarmCommentUpdateMsg;
import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg; import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.EdgeVersion;
@ -29,4 +30,8 @@ public interface AlarmProcessor extends EdgeProcessor {
ListenableFuture<Void> processAlarmMsgFromEdge(TenantId tenantId, EdgeId edgeId, AlarmUpdateMsg alarmUpdateMsg); ListenableFuture<Void> processAlarmMsgFromEdge(TenantId tenantId, EdgeId edgeId, AlarmUpdateMsg alarmUpdateMsg);
DownlinkMsg convertAlarmEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion); DownlinkMsg convertAlarmEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion);
ListenableFuture<Void> processAlarmCommentMsgFromEdge(TenantId tenantId, EdgeId edgeId, AlarmCommentUpdateMsg alarmCommentUpdateMsg);
DownlinkMsg convertAlarmCommentEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion);
} }

43
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/BaseAlarmProcessor.java

@ -19,10 +19,12 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.alarm.Alarm; 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.AlarmCreateOrUpdateActiveRequest;
import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest; import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest;
import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.Asset;
@ -33,6 +35,8 @@ import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.TenantId; 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.AlarmUpdateMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
@ -44,6 +48,9 @@ import java.util.UUID;
@Slf4j @Slf4j
public abstract class BaseAlarmProcessor extends BaseEdgeProcessor { public abstract class BaseAlarmProcessor extends BaseEdgeProcessor {
@Autowired
protected AlarmCommentDao alarmCommentDao;
public ListenableFuture<Void> processAlarmMsg(TenantId tenantId, AlarmUpdateMsg alarmUpdateMsg) { public ListenableFuture<Void> processAlarmMsg(TenantId tenantId, AlarmUpdateMsg alarmUpdateMsg) {
log.trace("[{}] processAlarmMsg [{}]", tenantId, alarmUpdateMsg); log.trace("[{}] processAlarmMsg [{}]", tenantId, alarmUpdateMsg);
AlarmId alarmId = new AlarmId(new UUID(alarmUpdateMsg.getIdMSB(), alarmUpdateMsg.getIdLSB())); AlarmId alarmId = new AlarmId(new UUID(alarmUpdateMsg.getIdMSB(), alarmUpdateMsg.getIdLSB()));
@ -93,6 +100,42 @@ public abstract class BaseAlarmProcessor extends BaseEdgeProcessor {
return Futures.immediateFuture(null); return Futures.immediateFuture(null);
} }
public ListenableFuture<Void> processAlarmCommentMsg(TenantId tenantId, AlarmCommentUpdateMsg alarmCommentUpdateMsg) {
log.trace("[{}] processAlarmCommentMsg [{}]", tenantId, alarmCommentUpdateMsg);
AlarmComment alarmComment = JacksonUtil.fromString(alarmCommentUpdateMsg.getEntity(), AlarmComment.class, true);
if (alarmComment == null) {
throw new RuntimeException("[{" + tenantId + "}] alarmCommentUpdateMsg {" + alarmCommentUpdateMsg + "} cannot be converted to alarm comment");
}
try {
Alarm alarm = alarmService.findAlarmById(tenantId, new AlarmId(alarmComment.getAlarmId().getId()));
if (alarm == null) {
return Futures.immediateFuture(null);
}
switch (alarmCommentUpdateMsg.getMsgType()) {
case ENTITY_CREATED_RPC_MESSAGE:
alarmCommentDao.createAlarmComment(tenantId, alarmComment);
break;
case ENTITY_UPDATED_RPC_MESSAGE:
alarmCommentService.createOrUpdateAlarmComment(tenantId, alarmComment);
break;
case ENTITY_DELETED_RPC_MESSAGE:
AlarmComment alarmCommentToDelete = alarmCommentService.findAlarmCommentById(tenantId, alarmComment.getId());
if (alarmCommentToDelete != null) {
alarmCommentService.saveAlarmComment(tenantId, alarmCommentToDelete);
}
break;
case UNRECOGNIZED:
default:
return handleUnsupportedMsgType(alarmCommentUpdateMsg.getMsgType());
}
} catch (Exception e) {
log.error("[{}] Failed to process alarm comment update msg [{}]", tenantId, alarmCommentUpdateMsg, e);
return Futures.immediateFailedFuture(e);
}
return Futures.immediateFuture(null);
}
protected abstract EntityId getAlarmOriginatorFromMsg(TenantId tenantId, AlarmUpdateMsg alarmUpdateMsg); protected abstract EntityId getAlarmOriginatorFromMsg(TenantId tenantId, AlarmUpdateMsg alarmUpdateMsg);
protected abstract Alarm constructAlarmFromUpdateMsg(TenantId tenantId, AlarmId alarmId, EntityId originatorId, AlarmUpdateMsg alarmUpdateMsg); protected abstract Alarm constructAlarmFromUpdateMsg(TenantId tenantId, AlarmId alarmId, EntityId originatorId, AlarmUpdateMsg alarmUpdateMsg);

38
application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java

@ -19,7 +19,6 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.msg.rule.engine.DeviceCredentialsUpdateNotificationMsg;
import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasName;
@ -27,7 +26,6 @@ import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EdgeId;
@ -40,6 +38,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.rule.engine.DeviceCredentialsUpdateNotificationMsg;
import org.thingsboard.server.service.action.EntityActionService; import org.thingsboard.server.service.action.EntityActionService;
import org.thingsboard.server.service.gateway_device.GatewayNotificationsService; import org.thingsboard.server.service.gateway_device.GatewayNotificationsService;
@ -174,39 +173,4 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS
metaData.putValue("assignedFromTenantName", tenant.getName()); metaData.putValue("assignedFromTenantName", tenant.getName());
return metaData; return metaData;
} }
public static EdgeEventActionType edgeTypeByActionType(ActionType actionType) {
switch (actionType) {
case ADDED:
return EdgeEventActionType.ADDED;
case UPDATED:
return EdgeEventActionType.UPDATED;
case ALARM_ACK:
return EdgeEventActionType.ALARM_ACK;
case ALARM_CLEAR:
return EdgeEventActionType.ALARM_CLEAR;
case ALARM_ASSIGNED:
return EdgeEventActionType.ALARM_ASSIGNED;
case ALARM_UNASSIGNED:
return EdgeEventActionType.ALARM_UNASSIGNED;
case DELETED:
return EdgeEventActionType.DELETED;
case RELATION_ADD_OR_UPDATE:
return EdgeEventActionType.RELATION_ADD_OR_UPDATE;
case RELATION_DELETED:
return EdgeEventActionType.RELATION_DELETED;
case ASSIGNED_TO_CUSTOMER:
return EdgeEventActionType.ASSIGNED_TO_CUSTOMER;
case UNASSIGNED_FROM_CUSTOMER:
return EdgeEventActionType.UNASSIGNED_FROM_CUSTOMER;
case ASSIGNED_TO_EDGE:
return EdgeEventActionType.ASSIGNED_TO_EDGE;
case UNASSIGNED_FROM_EDGE:
return EdgeEventActionType.UNASSIGNED_FROM_EDGE;
case CREDENTIALS_UPDATED:
return EdgeEventActionType.CREDENTIALS_UPDATED;
default:
return null;
}
}
} }

1
application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmCommentService.java

@ -21,6 +21,7 @@ import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
public interface TbAlarmCommentService { public interface TbAlarmCommentService {
AlarmComment saveAlarmComment(Alarm alarm, AlarmComment alarmComment, User user) throws ThingsboardException; AlarmComment saveAlarmComment(Alarm alarm, AlarmComment alarmComment, User user) throws ThingsboardException;
void deleteAlarmComment(Alarm alarm, AlarmComment alarmComment, User user) throws ThingsboardException; void deleteAlarmComment(Alarm alarm, AlarmComment alarmComment, User user) throws ThingsboardException;

7
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EntitiesLimitTriggerProcessor.java

@ -19,10 +19,10 @@ import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.notification.info.EntitiesLimitNotificationInfo; import org.thingsboard.server.common.data.notification.info.EntitiesLimitNotificationInfo;
import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo; import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.EntitiesLimitTrigger;
import org.thingsboard.server.common.data.notification.rule.trigger.config.EntitiesLimitNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.config.EntitiesLimitNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerType; import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerType;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.common.data.notification.rule.trigger.EntitiesLimitTrigger;
import org.thingsboard.server.dao.entity.EntityCountService; import org.thingsboard.server.dao.entity.EntityCountService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.tenant.TenantService;
@ -48,6 +48,9 @@ public class EntitiesLimitTriggerProcessor implements NotificationRuleTriggerPro
return false; return false;
} }
long currentCount = entityCountService.countByTenantIdAndEntityType(trigger.getTenantId(), trigger.getEntityType()); long currentCount = entityCountService.countByTenantIdAndEntityType(trigger.getTenantId(), trigger.getEntityType());
if (currentCount == 0) {
return false;
}
trigger.setLimit(limit); trigger.setLimit(limit);
trigger.setCurrentCount(currentCount); trigger.setCurrentCount(currentCount);
return (int) (limit * triggerConfig.getThreshold()) == currentCount; // strict comparing not to send notification on each new entity return (int) (limit * triggerConfig.getThreshold()) == currentCount; // strict comparing not to send notification on each new entity
@ -59,7 +62,7 @@ public class EntitiesLimitTriggerProcessor implements NotificationRuleTriggerPro
.entityType(trigger.getEntityType()) .entityType(trigger.getEntityType())
.currentCount(trigger.getCurrentCount()) .currentCount(trigger.getCurrentCount())
.limit(trigger.getLimit()) .limit(trigger.getLimit())
.percents((int) (((float)trigger.getCurrentCount() / trigger.getLimit()) * 100)) .percents((int) (((float) trigger.getCurrentCount() / trigger.getLimit()) * 100))
.tenantId(trigger.getTenantId()) .tenantId(trigger.getTenantId())
.tenantName(tenantService.findTenantById(trigger.getTenantId()).getName()) .tenantName(tenantService.findTenantById(trigger.getTenantId()).getName())
.build(); .build();

4
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java

@ -373,10 +373,6 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
} else if (toCoreNotification.hasComponentLifecycle()) { } else if (toCoreNotification.hasComponentLifecycle()) {
handleComponentLifecycleMsg(id, ProtoUtils.fromProto(toCoreNotification.getComponentLifecycle())); handleComponentLifecycleMsg(id, ProtoUtils.fromProto(toCoreNotification.getComponentLifecycle()));
callback.onSuccess(); callback.onSuccess();
} else if (!toCoreNotification.getComponentLifecycleMsg().isEmpty()) {
//will be removed in 3.6.1 in favour of hasComponentLifecycle()
handleComponentLifecycleMsg(id, toCoreNotification.getComponentLifecycleMsg());
callback.onSuccess();
} else if (toCoreNotification.hasEdgeEventUpdate()) { } else if (toCoreNotification.hasEdgeEventUpdate()) {
forwardToAppActor(id, ProtoUtils.fromProto(toCoreNotification.getEdgeEventUpdate())); forwardToAppActor(id, ProtoUtils.fromProto(toCoreNotification.getEdgeEventUpdate()));
callback.onSuccess(); callback.onSuccess();

32
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java

@ -18,13 +18,17 @@ package org.thingsboard.server.service.queue;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.QueueId; import org.thingsboard.server.common.data.id.QueueId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.data.queue.Queue; import org.thingsboard.server.common.data.queue.Queue;
import org.thingsboard.server.common.data.rpc.RpcError; import org.thingsboard.server.common.data.rpc.RpcError;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse; import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse;
@ -56,6 +60,7 @@ import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;
@Service @Service
@TbRuleEngineComponent @TbRuleEngineComponent
@ -152,10 +157,6 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
if (nfMsg.hasComponentLifecycle()) { if (nfMsg.hasComponentLifecycle()) {
handleComponentLifecycleMsg(id, ProtoUtils.fromProto(nfMsg.getComponentLifecycle())); handleComponentLifecycleMsg(id, ProtoUtils.fromProto(nfMsg.getComponentLifecycle()));
callback.onSuccess(); callback.onSuccess();
} else if (!nfMsg.getComponentLifecycleMsg().isEmpty()) {
//will be removed in 3.6.1 in favour of hasComponentLifecycle()
handleComponentLifecycleMsg(id, nfMsg.getComponentLifecycleMsg());
callback.onSuccess();
} else if (nfMsg.hasFromDeviceRpcResponse()) { } else if (nfMsg.hasFromDeviceRpcResponse()) {
TransportProtos.FromDeviceRPCResponseProto proto = nfMsg.getFromDeviceRpcResponse(); TransportProtos.FromDeviceRPCResponseProto proto = nfMsg.getFromDeviceRpcResponse();
RpcError error = proto.getError() > 0 ? RpcError.values()[proto.getError()] : null; RpcError error = proto.getError() > 0 ? RpcError.values()[proto.getError()] : null;
@ -164,10 +165,10 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
tbDeviceRpcService.processRpcResponseFromDevice(response); tbDeviceRpcService.processRpcResponseFromDevice(response);
callback.onSuccess(); callback.onSuccess();
} else if (nfMsg.hasQueueUpdateMsg()) { } else if (nfMsg.hasQueueUpdateMsg()) {
ctx.getScheduler().execute(() -> updateQueue(nfMsg.getQueueUpdateMsg())); updateQueue(nfMsg.getQueueUpdateMsg());
callback.onSuccess(); callback.onSuccess();
} else if (nfMsg.hasQueueDeleteMsg()) { } else if (nfMsg.hasQueueDeleteMsg()) {
ctx.getScheduler().execute(() -> deleteQueue(nfMsg.getQueueDeleteMsg())); deleteQueue(nfMsg.getQueueDeleteMsg());
callback.onSuccess(); callback.onSuccess();
} else { } else {
log.trace("Received notification with missing handler"); log.trace("Received notification with missing handler");
@ -204,13 +205,30 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
QueueKey queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, queueDeleteMsg.getQueueName(), tenantId); QueueKey queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, queueDeleteMsg.getQueueName(), tenantId);
var consumerManager = consumers.remove(queueKey); var consumerManager = consumers.remove(queueKey);
if (consumerManager != null) { if (consumerManager != null) {
consumerManager.delete(); consumerManager.delete(true);
} }
partitionService.removeQueue(queueDeleteMsg); partitionService.removeQueue(queueDeleteMsg);
partitionService.recalculatePartitions(ctx.getServiceInfoProvider().getServiceInfo(), new ArrayList<>(partitionService.getOtherServices(ServiceType.TB_RULE_ENGINE))); partitionService.recalculatePartitions(ctx.getServiceInfoProvider().getServiceInfo(), new ArrayList<>(partitionService.getOtherServices(ServiceType.TB_RULE_ENGINE)));
} }
@EventListener
public void handleComponentLifecycleEvent(ComponentLifecycleMsg event) {
if (event.getEntityId().getEntityType() == EntityType.TENANT) {
if (event.getEvent() == ComponentLifecycleEvent.DELETED) {
List<QueueKey> toRemove = consumers.keySet().stream()
.filter(queueKey -> queueKey.getTenantId().equals(event.getTenantId()))
.collect(Collectors.toList());
toRemove.forEach(queueKey -> {
var consumerManager = consumers.remove(queueKey);
if (consumerManager != null) {
consumerManager.delete(false);
}
});
}
}
}
private TbRuleEngineQueueConsumerManager getOrCreateConsumer(QueueKey queueKey) { private TbRuleEngineQueueConsumerManager getOrCreateConsumer(QueueKey queueKey) {
return consumers.computeIfAbsent(queueKey, key -> new TbRuleEngineQueueConsumerManager(ctx, key)); return consumers.computeIfAbsent(queueKey, key -> new TbRuleEngineQueueConsumerManager(ctx, key));
} }

90
application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java

@ -15,7 +15,6 @@
*/ */
package org.thingsboard.server.service.queue.processing; package org.thingsboard.server.service.queue.processing;
import com.google.protobuf.ByteString;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
@ -30,7 +29,6 @@ import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TbCallback;
@ -166,57 +164,51 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene
}); });
} }
// To be removed in 3.6.1 in favour of handleComponentLifecycleMsg(UUID id, TbActorMsg actorMsg) protected final void handleComponentLifecycleMsg(UUID id, ComponentLifecycleMsg componentLifecycleMsg) {
protected void handleComponentLifecycleMsg(UUID id, ByteString nfMsg) { TenantId tenantId = componentLifecycleMsg.getTenantId();
Optional<TbActorMsg> actorMsgOpt = encodingService.decode(nfMsg.toByteArray()); log.debug("[{}][{}][{}] Received Lifecycle event: {}", tenantId, componentLifecycleMsg.getEntityId().getEntityType(),
actorMsgOpt.ifPresent(tbActorMsg -> handleComponentLifecycleMsg(id, tbActorMsg)); componentLifecycleMsg.getEntityId(), componentLifecycleMsg.getEvent());
} if (EntityType.TENANT_PROFILE.equals(componentLifecycleMsg.getEntityId().getEntityType())) {
TenantProfileId tenantProfileId = new TenantProfileId(componentLifecycleMsg.getEntityId().getId());
protected void handleComponentLifecycleMsg(UUID id, TbActorMsg actorMsg) { tenantProfileCache.evict(tenantProfileId);
if (actorMsg instanceof ComponentLifecycleMsg) { if (componentLifecycleMsg.getEvent().equals(ComponentLifecycleEvent.UPDATED)) {
ComponentLifecycleMsg componentLifecycleMsg = (ComponentLifecycleMsg) actorMsg; apiUsageStateService.onTenantProfileUpdate(tenantProfileId);
log.debug("[{}][{}][{}] Received Lifecycle event: {}", componentLifecycleMsg.getTenantId(), componentLifecycleMsg.getEntityId().getEntityType(), }
componentLifecycleMsg.getEntityId(), componentLifecycleMsg.getEvent()); } else if (EntityType.TENANT.equals(componentLifecycleMsg.getEntityId().getEntityType())) {
if (EntityType.TENANT_PROFILE.equals(componentLifecycleMsg.getEntityId().getEntityType())) { if (TenantId.SYS_TENANT_ID.equals(tenantId)) {
TenantProfileId tenantProfileId = new TenantProfileId(componentLifecycleMsg.getEntityId().getId()); jwtSettingsService.ifPresent(JwtSettingsService::reloadJwtSettings);
tenantProfileCache.evict(tenantProfileId); return;
} else {
tenantProfileCache.evict(tenantId);
partitionService.evictTenantInfo(tenantId);
if (componentLifecycleMsg.getEvent().equals(ComponentLifecycleEvent.UPDATED)) { if (componentLifecycleMsg.getEvent().equals(ComponentLifecycleEvent.UPDATED)) {
apiUsageStateService.onTenantProfileUpdate(tenantProfileId); apiUsageStateService.onTenantUpdate(tenantId);
} } else if (componentLifecycleMsg.getEvent().equals(ComponentLifecycleEvent.DELETED)) {
} else if (EntityType.TENANT.equals(componentLifecycleMsg.getEntityId().getEntityType())) { apiUsageStateService.onTenantDelete(tenantId);
if (TenantId.SYS_TENANT_ID.equals(componentLifecycleMsg.getTenantId())) { partitionService.removeTenant(tenantId);
jwtSettingsService.ifPresent(JwtSettingsService::reloadJwtSettings);
return;
} else {
tenantProfileCache.evict(componentLifecycleMsg.getTenantId());
partitionService.removeTenant(componentLifecycleMsg.getTenantId());
if (componentLifecycleMsg.getEvent().equals(ComponentLifecycleEvent.UPDATED)) {
apiUsageStateService.onTenantUpdate(componentLifecycleMsg.getTenantId());
} else if (componentLifecycleMsg.getEvent().equals(ComponentLifecycleEvent.DELETED)) {
apiUsageStateService.onTenantDelete((TenantId) componentLifecycleMsg.getEntityId());
}
}
} else if (EntityType.DEVICE_PROFILE.equals(componentLifecycleMsg.getEntityId().getEntityType())) {
deviceProfileCache.evict(componentLifecycleMsg.getTenantId(), new DeviceProfileId(componentLifecycleMsg.getEntityId().getId()));
} else if (EntityType.DEVICE.equals(componentLifecycleMsg.getEntityId().getEntityType())) {
deviceProfileCache.evict(componentLifecycleMsg.getTenantId(), new DeviceId(componentLifecycleMsg.getEntityId().getId()));
} else if (EntityType.ASSET_PROFILE.equals(componentLifecycleMsg.getEntityId().getEntityType())) {
assetProfileCache.evict(componentLifecycleMsg.getTenantId(), new AssetProfileId(componentLifecycleMsg.getEntityId().getId()));
} else if (EntityType.ASSET.equals(componentLifecycleMsg.getEntityId().getEntityType())) {
assetProfileCache.evict(componentLifecycleMsg.getTenantId(), new AssetId(componentLifecycleMsg.getEntityId().getId()));
} else if (EntityType.ENTITY_VIEW.equals(componentLifecycleMsg.getEntityId().getEntityType())) {
actorContext.getTbEntityViewService().onComponentLifecycleMsg(componentLifecycleMsg);
} else if (EntityType.API_USAGE_STATE.equals(componentLifecycleMsg.getEntityId().getEntityType())) {
apiUsageStateService.onApiUsageStateUpdate(componentLifecycleMsg.getTenantId());
} else if (EntityType.CUSTOMER.equals(componentLifecycleMsg.getEntityId().getEntityType())) {
if (componentLifecycleMsg.getEvent() == ComponentLifecycleEvent.DELETED) {
apiUsageStateService.onCustomerDelete((CustomerId) componentLifecycleMsg.getEntityId());
} }
} }
eventPublisher.publishEvent(componentLifecycleMsg); } else if (EntityType.DEVICE_PROFILE.equals(componentLifecycleMsg.getEntityId().getEntityType())) {
deviceProfileCache.evict(tenantId, new DeviceProfileId(componentLifecycleMsg.getEntityId().getId()));
} else if (EntityType.DEVICE.equals(componentLifecycleMsg.getEntityId().getEntityType())) {
deviceProfileCache.evict(tenantId, new DeviceId(componentLifecycleMsg.getEntityId().getId()));
} else if (EntityType.ASSET_PROFILE.equals(componentLifecycleMsg.getEntityId().getEntityType())) {
assetProfileCache.evict(tenantId, new AssetProfileId(componentLifecycleMsg.getEntityId().getId()));
} else if (EntityType.ASSET.equals(componentLifecycleMsg.getEntityId().getEntityType())) {
assetProfileCache.evict(tenantId, new AssetId(componentLifecycleMsg.getEntityId().getId()));
} else if (EntityType.ENTITY_VIEW.equals(componentLifecycleMsg.getEntityId().getEntityType())) {
actorContext.getTbEntityViewService().onComponentLifecycleMsg(componentLifecycleMsg);
} else if (EntityType.API_USAGE_STATE.equals(componentLifecycleMsg.getEntityId().getEntityType())) {
apiUsageStateService.onApiUsageStateUpdate(tenantId);
} else if (EntityType.CUSTOMER.equals(componentLifecycleMsg.getEntityId().getEntityType())) {
if (componentLifecycleMsg.getEvent() == ComponentLifecycleEvent.DELETED) {
apiUsageStateService.onCustomerDelete((CustomerId) componentLifecycleMsg.getEntityId());
}
} }
log.trace("[{}] Forwarding component lifecycle message to App Actor {}", id, actorMsg);
actorContext.tellWithHighPriority(actorMsg); eventPublisher.publishEvent(componentLifecycleMsg);
log.trace("[{}] Forwarding component lifecycle message to App Actor {}", id, componentLifecycleMsg);
actorContext.tellWithHighPriority(componentLifecycleMsg);
} }
protected abstract void handleNotification(UUID id, TbProtoQueueMsg<N> msg, TbCallback callback) throws Exception; protected abstract void handleNotification(UUID id, TbProtoQueueMsg<N> msg, TbCallback callback) throws Exception;

17
application/src/main/java/org/thingsboard/server/service/queue/ruleengine/TbQueueConsumerManagerTask.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.service.queue.ruleengine; package org.thingsboard.server.service.queue.ruleengine;
import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.ToString; import lombok.ToString;
import org.thingsboard.server.common.data.queue.Queue; import org.thingsboard.server.common.data.queue.Queue;
@ -24,24 +25,24 @@ import java.util.Set;
@Getter @Getter
@ToString @ToString
@AllArgsConstructor
public class TbQueueConsumerManagerTask { public class TbQueueConsumerManagerTask {
private final QueueEvent event; private final QueueEvent event;
private Queue queue; private Queue queue;
private Set<TopicPartitionInfo> partitions; private Set<TopicPartitionInfo> partitions;
private boolean drainQueue;
public TbQueueConsumerManagerTask(QueueEvent event) { public static TbQueueConsumerManagerTask delete(boolean drainQueue) {
this.event = event; return new TbQueueConsumerManagerTask(QueueEvent.DELETE, null, null, drainQueue);
} }
public TbQueueConsumerManagerTask(QueueEvent event, Queue queue) { public static TbQueueConsumerManagerTask configUpdate(Queue queue) {
this.event = event; return new TbQueueConsumerManagerTask(QueueEvent.CONFIG_UPDATE, queue, null, false);
this.queue = queue;
} }
public TbQueueConsumerManagerTask(QueueEvent event, Set<TopicPartitionInfo> partitions) { public static TbQueueConsumerManagerTask partitionChange(Set<TopicPartitionInfo> partitions) {
this.event = event; return new TbQueueConsumerManagerTask(QueueEvent.PARTITION_CHANGE, null, partitions, false);
this.partitions = partitions;
} }
} }

16
application/src/main/java/org/thingsboard/server/service/queue/ruleengine/TbRuleEngineQueueConsumerManager.java

@ -95,15 +95,15 @@ public class TbRuleEngineQueueConsumerManager {
} }
public void update(Queue queue) { public void update(Queue queue) {
addTask(new TbQueueConsumerManagerTask(QueueEvent.CONFIG_UPDATE, queue)); addTask(TbQueueConsumerManagerTask.configUpdate(queue));
} }
public void update(Set<TopicPartitionInfo> partitions) { public void update(Set<TopicPartitionInfo> partitions) {
addTask(new TbQueueConsumerManagerTask(QueueEvent.PARTITION_CHANGE, partitions)); addTask(TbQueueConsumerManagerTask.partitionChange(partitions));
} }
public void delete() { public void delete(boolean drainQueue) {
addTask(new TbQueueConsumerManagerTask(QueueEvent.DELETE)); addTask(TbQueueConsumerManagerTask.delete(drainQueue));
} }
private void addTask(TbQueueConsumerManagerTask todo) { private void addTask(TbQueueConsumerManagerTask todo) {
@ -138,7 +138,7 @@ public class TbRuleEngineQueueConsumerManager {
} else if (task.getEvent() == QueueEvent.CONFIG_UPDATE) { } else if (task.getEvent() == QueueEvent.CONFIG_UPDATE) {
newConfiguration = task.getQueue(); newConfiguration = task.getQueue();
} else if (task.getEvent() == QueueEvent.DELETE) { } else if (task.getEvent() == QueueEvent.DELETE) {
doDelete(); doDelete(task.isDrainQueue());
return; return;
} }
} }
@ -205,7 +205,7 @@ public class TbRuleEngineQueueConsumerManager {
log.debug("[{}] Unsubscribed and stopped consumers", queueKey); log.debug("[{}] Unsubscribed and stopped consumers", queueKey);
} }
private void doDelete() { private void doDelete(boolean drainQueue) {
stopped = true; stopped = true;
log.info("[{}] Handling queue deletion", queueKey); log.info("[{}] Handling queue deletion", queueKey);
consumerWrapper.getConsumers().forEach(TbQueueConsumerTask::awaitCompletion); consumerWrapper.getConsumers().forEach(TbQueueConsumerTask::awaitCompletion);
@ -213,7 +213,9 @@ public class TbRuleEngineQueueConsumerManager {
List<TbQueueConsumer<TbProtoQueueMsg<ToRuleEngineMsg>>> queueConsumers = consumerWrapper.getConsumers().stream() List<TbQueueConsumer<TbProtoQueueMsg<ToRuleEngineMsg>>> queueConsumers = consumerWrapper.getConsumers().stream()
.map(TbQueueConsumerTask::getConsumer).collect(Collectors.toList()); .map(TbQueueConsumerTask::getConsumer).collect(Collectors.toList());
ctx.getConsumersExecutor().submit(() -> { ctx.getConsumersExecutor().submit(() -> {
drainQueue(queueConsumers); if (drainQueue) {
drainQueue(queueConsumers);
}
queueConsumers.forEach(consumer -> { queueConsumers.forEach(consumer -> {
for (String topic : consumer.getFullTopicNames()) { for (String topic : consumer.getFullTopicNames()) {

18
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java

@ -38,7 +38,6 @@ import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
import org.thingsboard.server.common.data.oauth2.OAuth2Registration; import org.thingsboard.server.common.data.oauth2.OAuth2Registration;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.customer.CustomerService;
@ -47,12 +46,12 @@ import org.thingsboard.server.dao.oauth2.OAuth2User;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.service.entitiy.tenant.TbTenantService;
import org.thingsboard.server.service.entitiy.user.TbUserService; import org.thingsboard.server.service.entitiy.user.TbUserService;
import org.thingsboard.server.service.install.InstallScripts; import org.thingsboard.server.service.install.InstallScripts;
import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.UserPrincipal; import org.thingsboard.server.service.security.model.UserPrincipal;
import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
@ -71,6 +70,9 @@ public abstract class AbstractOAuth2ClientMapper {
@Autowired @Autowired
private TenantService tenantService; private TenantService tenantService;
@Autowired
private TbTenantService tbTenantService;
@Autowired @Autowired
private CustomerService customerService; private CustomerService customerService;
@ -92,7 +94,7 @@ public abstract class AbstractOAuth2ClientMapper {
@Value("${edges.enabled}") @Value("${edges.enabled}")
@Getter @Getter
private boolean edgesEnabled; private boolean edgesEnabled;
private final Lock userCreationLock = new ReentrantLock(); private final Lock userCreationLock = new ReentrantLock();
protected SecurityUser getOrCreateSecurityUserFromOAuth2User(OAuth2User oauth2User, OAuth2Registration registration) { protected SecurityUser getOrCreateSecurityUserFromOAuth2User(OAuth2User oauth2User, OAuth2Registration registration) {
@ -171,19 +173,13 @@ public abstract class AbstractOAuth2ClientMapper {
} }
} }
private TenantId getTenantId(String tenantName) throws IOException { private TenantId getTenantId(String tenantName) throws Exception {
List<Tenant> tenants = tenantService.findTenants(new PageLink(1, 0, tenantName)).getData(); List<Tenant> tenants = tenantService.findTenants(new PageLink(1, 0, tenantName)).getData();
Tenant tenant; Tenant tenant;
if (tenants == null || tenants.isEmpty()) { if (tenants == null || tenants.isEmpty()) {
tenant = new Tenant(); tenant = new Tenant();
tenant.setTitle(tenantName); tenant.setTitle(tenantName);
tenant = tenantService.saveTenant(tenant); tenant = tbTenantService.save(tenant);
installScripts.createDefaultRuleChains(tenant.getId());
installScripts.createDefaultEdgeRuleChains(tenant.getId());
tenantProfileCache.evict(tenant.getId());
tbClusterService.onTenantChange(tenant, null);
tbClusterService.broadcastEntityStateChangeEvent(tenant.getId(), tenant.getId(),
ComponentLifecycleEvent.CREATED);
} else { } else {
tenant = tenants.get(0); tenant = tenants.get(0);
} }

18
application/src/main/resources/thingsboard.yml

@ -777,7 +777,12 @@ audit-log:
# Device state parameters # Device state parameters
state: state:
# Should be greater than transport.sessions.report_timeout # Device inactivity timeout is a global configuration parameter that defines when the device will be marked as "inactive" by the server.
# The parameter value is in seconds. A user can overwrite this parameter for an individual device by setting the “inactivityTimeout” server-side attribute (NOTE: expects value in milliseconds).
# We recommend this parameter to be in sync with session inactivity timeout ("transport.sessions.inactivity_timeout" or TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT) parameter
# which is responsible for detection of the stale device connection sessions.
# The value of the session inactivity timeout parameter should be greater or equal to the device inactivity timeout.
# Note that the session inactivity timeout is set in milliseconds while device inactivity timeout is in seconds.
defaultInactivityTimeoutInSec: "${DEFAULT_INACTIVITY_TIMEOUT:600}" defaultInactivityTimeoutInSec: "${DEFAULT_INACTIVITY_TIMEOUT:600}"
defaultStateCheckIntervalInSec: "${DEFAULT_STATE_CHECK_INTERVAL:60}" # Interval for checking the device state after a specified period. Time in seconds defaultStateCheckIntervalInSec: "${DEFAULT_STATE_CHECK_INTERVAL:60}" # Interval for checking the device state after a specified period. Time in seconds
# Controls whether we store the device 'active' flag in attributes (default) or telemetry. # Controls whether we store the device 'active' flag in attributes (default) or telemetry.
@ -866,8 +871,15 @@ js:
# Transport configuration parameters # Transport configuration parameters
transport: transport:
sessions: sessions:
# Inactivity timeout for device session in transport service. The last activity time of the device session is updated if the device sends any message, including keepalive messages # Session inactivity timeout is a global configuration parameter that defines how long the device transport session will be opened after the last message arrives from the device.
inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}" # The parameter value is in milliseconds.
# The last activity time of the device session is updated if the device sends any message, including keepalive messages
# If there is no activity, the session will be closed, and all subscriptions will be deleted.
# We recommend this parameter to be in sync with device inactivity timeout ("state.defaultInactivityTimeoutInSec" or DEFAULT_INACTIVITY_TIMEOUT) parameter
# which is responsible for detection of the device connectivity status in the core service of the platform.
# The value of the session inactivity timeout parameter should be greater or equal to the device inactivity timeout.
# Note that the session inactivity timeout is set in milliseconds while device inactivity timeout is in seconds.
inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:600000}"
# Interval of periodic check for expired sessions and report of the changes to session last activity time # Interval of periodic check for expired sessions and report of the changes to session last activity time
report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:3000}" report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:3000}"
activity: activity:

74
application/src/test/java/org/thingsboard/server/actors/tenant/TenantActorTest.java

@ -0,0 +1,74 @@
/**
* 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.tenant;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.TbActorCtx;
import org.thingsboard.server.actors.TbActorRef;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.common.msg.rule.engine.DeviceDeleteMsg;
import org.thingsboard.server.dao.tenant.TenantService;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class TenantActorTest {
TenantActor tenantActor;
TbActorCtx ctx;
ActorSystemContext systemContext;
TenantId tenantId = TenantId.SYS_TENANT_ID;
DeviceId deviceId = DeviceId.fromString("78bf9b26-74ef-4af2-9cfb-ad6cf24ad2ec");
@Before
public void setUp() throws Exception {
systemContext = mock(ActorSystemContext.class);
ctx = mock(TbActorCtx.class);
tenantActor = (TenantActor) new TenantActor.ActorCreator(systemContext, tenantId).createActor();
when(systemContext.getTenantService()).thenReturn(mock(TenantService.class));
tenantActor.init(ctx);
tenantActor.cantFindTenant = false;
}
@Test
public void deleteDeviceTest() {
TbActorRef deviceActorRef = mock(TbActorRef.class);
when(systemContext.resolve(ServiceType.TB_CORE, tenantId, deviceId)).thenReturn(new TopicPartitionInfo("Main", tenantId, 0,true));
when(ctx.getOrCreateChildActor(any(), any(), any(), any())).thenReturn(deviceActorRef);
ComponentLifecycleMsg componentLifecycleMsg = new ComponentLifecycleMsg(tenantId, deviceId, ComponentLifecycleEvent.DELETED);
tenantActor.doProcess(componentLifecycleMsg);
verify(deviceActorRef).tellWithHighPriority(eq(new DeviceDeleteMsg(tenantId, deviceId)));
reset(ctx, deviceActorRef);
when(systemContext.resolve(ServiceType.TB_CORE, tenantId, deviceId)).thenReturn(new TopicPartitionInfo("Main", tenantId, 1,false));
tenantActor.doProcess(componentLifecycleMsg);
verify(ctx, never()).getOrCreateChildActor(any(), any(), any(), any());
verify(deviceActorRef, never()).tellWithHighPriority(any());
}
}

12
application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java

@ -20,6 +20,7 @@ import org.mockito.ArgumentMatcher;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.boot.test.mock.mockito.SpyBean;
import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.HasTenantId;
@ -47,7 +48,6 @@ import java.util.stream.Collectors;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.thingsboard.server.service.entitiy.DefaultTbNotificationEntityService.edgeTypeByActionType;
@Slf4j @Slf4j
public abstract class AbstractNotifyEntityTest extends AbstractWebTest { public abstract class AbstractNotifyEntityTest extends AbstractWebTest {
@ -91,7 +91,7 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest {
int cntTime = 1; int cntTime = 1;
Mockito.verify(tbClusterService, times(cntTime)).sendNotificationMsgToEdge(Mockito.eq(tenantId), Mockito.verify(tbClusterService, times(cntTime)).sendNotificationMsgToEdge(Mockito.eq(tenantId),
Mockito.isNull(), Mockito.isNull(), Mockito.any(), Mockito.eq(EdgeEventType.RELATION), Mockito.isNull(), Mockito.isNull(), Mockito.any(), Mockito.eq(EdgeEventType.RELATION),
Mockito.eq(edgeTypeByActionType(actionType)), Mockito.any()); Mockito.eq(EdgeUtils.getEdgeEventActionTypeByActionType(actionType)), Mockito.any());
ArgumentMatcher<EntityId> matcherOriginatorId = argument -> argument.equals(relation.getTo()); ArgumentMatcher<EntityId> matcherOriginatorId = argument -> argument.equals(relation.getTo());
ArgumentMatcher<HasName> matcherEntityClassEquals = Objects::isNull; ArgumentMatcher<HasName> matcherEntityClassEquals = Objects::isNull;
ArgumentMatcher<CustomerId> matcherCustomerId = customerId == null ? ArgumentMatcher<CustomerId> matcherCustomerId = customerId == null ?
@ -111,7 +111,7 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest {
ActionType actionType, int cntTime) { ActionType actionType, int cntTime) {
Mockito.verify(tbClusterService, times(cntTime)).sendNotificationMsgToEdge(Mockito.eq(tenantId), Mockito.verify(tbClusterService, times(cntTime)).sendNotificationMsgToEdge(Mockito.eq(tenantId),
Mockito.isNull(), Mockito.isNull(), Mockito.any(), Mockito.eq(EdgeEventType.RELATION), Mockito.isNull(), Mockito.isNull(), Mockito.any(), Mockito.eq(EdgeEventType.RELATION),
Mockito.eq(edgeTypeByActionType(actionType)), Mockito.any()); Mockito.eq(EdgeUtils.getEdgeEventActionTypeByActionType(actionType)), Mockito.any());
ArgumentMatcher<EntityId> matcherOriginatorId = argument -> argument.getClass().equals(relation.getFrom().getClass()); ArgumentMatcher<EntityId> matcherOriginatorId = argument -> argument.getClass().equals(relation.getFrom().getClass());
ArgumentMatcher<HasName> matcherEntityClassEquals = Objects::isNull; ArgumentMatcher<HasName> matcherEntityClassEquals = Objects::isNull;
ArgumentMatcher<CustomerId> matcherCustomerId = customerId == null ? ArgumentMatcher<CustomerId> matcherCustomerId = customerId == null ?
@ -317,7 +317,7 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest {
private void testNotificationMsgToEdgeServiceNeverWithActionType(EntityId entityId, ActionType actionType) { private void testNotificationMsgToEdgeServiceNeverWithActionType(EntityId entityId, ActionType actionType) {
EdgeEventActionType edgeEventActionType = ActionType.CREDENTIALS_UPDATED.equals(actionType) ? EdgeEventActionType edgeEventActionType = ActionType.CREDENTIALS_UPDATED.equals(actionType) ?
EdgeEventActionType.CREDENTIALS_UPDATED : edgeTypeByActionType(actionType); EdgeEventActionType.CREDENTIALS_UPDATED : EdgeUtils.getEdgeEventActionTypeByActionType(actionType);
Mockito.verify(tbClusterService, never()).sendNotificationMsgToEdge(Mockito.any(), Mockito.any(), Mockito.verify(tbClusterService, never()).sendNotificationMsgToEdge(Mockito.any(), Mockito.any(),
Mockito.any(entityId.getClass()), Mockito.any(), Mockito.any(), Mockito.eq(edgeEventActionType), Mockito.any()); Mockito.any(entityId.getClass()), Mockito.any(), Mockito.any(), Mockito.eq(edgeEventActionType), Mockito.any());
} }
@ -353,7 +353,7 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest {
private void testNotificationMsgToEdgeServiceTime(EntityId entityId, TenantId tenantId, ActionType actionType, int cntTime) { private void testNotificationMsgToEdgeServiceTime(EntityId entityId, TenantId tenantId, ActionType actionType, int cntTime) {
EdgeEventActionType edgeEventActionType = ActionType.CREDENTIALS_UPDATED.equals(actionType) ? EdgeEventActionType edgeEventActionType = ActionType.CREDENTIALS_UPDATED.equals(actionType) ?
EdgeEventActionType.CREDENTIALS_UPDATED : edgeTypeByActionType(actionType); EdgeEventActionType.CREDENTIALS_UPDATED : EdgeUtils.getEdgeEventActionTypeByActionType(actionType);
ArgumentMatcher<EntityId> matcherEntityId = cntTime == 1 ? argument -> argument.equals(entityId) : ArgumentMatcher<EntityId> matcherEntityId = cntTime == 1 ? argument -> argument.equals(entityId) :
argument -> argument.getClass().equals(entityId.getClass()); argument -> argument.getClass().equals(entityId.getClass());
Mockito.verify(tbClusterService, times(cntTime)).sendNotificationMsgToEdge(Mockito.eq(tenantId), Mockito.verify(tbClusterService, times(cntTime)).sendNotificationMsgToEdge(Mockito.eq(tenantId),
@ -364,7 +364,7 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest {
private void testSendNotificationMsgToEdgeServiceTimeEntityEqAny(TenantId tenantId, ActionType actionType, int cntTime) { private void testSendNotificationMsgToEdgeServiceTimeEntityEqAny(TenantId tenantId, ActionType actionType, int cntTime) {
Mockito.verify(tbClusterService, times(cntTime)).sendNotificationMsgToEdge(Mockito.eq(tenantId), Mockito.verify(tbClusterService, times(cntTime)).sendNotificationMsgToEdge(Mockito.eq(tenantId),
Mockito.any(), Mockito.any(EntityId.class), Mockito.any(), Mockito.isNull(), Mockito.any(), Mockito.any(EntityId.class), Mockito.any(), Mockito.isNull(),
Mockito.eq(edgeTypeByActionType(actionType)), Mockito.any()); Mockito.eq(EdgeUtils.getEdgeEventActionTypeByActionType(actionType)), Mockito.any());
} }
protected void testBroadcastEntityStateChangeEventTime(EntityId entityId, TenantId tenantId, int cntTime) { protected void testBroadcastEntityStateChangeEventTime(EntityId entityId, TenantId tenantId, int cntTime) {

6
application/src/test/java/org/thingsboard/server/controller/AlarmControllerTest.java

@ -110,7 +110,7 @@ public class AlarmControllerTest extends AbstractControllerTest {
Alarm alarm = createAlarm(TEST_ALARM_TYPE); Alarm alarm = createAlarm(TEST_ALARM_TYPE);
testNotifyEntityAllOneTime(alarm, alarm.getId(), alarm.getOriginator(), testNotifyEntityOneTimeMsgToEdgeServiceNever(alarm, alarm.getId(), alarm.getOriginator(),
tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.ADDED); tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.ADDED);
} }
@ -122,7 +122,7 @@ public class AlarmControllerTest extends AbstractControllerTest {
Alarm alarm = createAlarm(TEST_ALARM_TYPE); Alarm alarm = createAlarm(TEST_ALARM_TYPE);
testNotifyEntityAllOneTime(alarm, alarm.getId(), alarm.getOriginator(), testNotifyEntityOneTimeMsgToEdgeServiceNever(alarm, alarm.getId(), alarm.getOriginator(),
tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ADDED); tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ADDED);
} }
@ -772,7 +772,7 @@ public class AlarmControllerTest extends AbstractControllerTest {
alarm = doPost("/api/alarm", alarm, Alarm.class); alarm = doPost("/api/alarm", alarm, Alarm.class);
Assert.assertNotNull("Saved alarm is null!", alarm); Assert.assertNotNull("Saved alarm is null!", alarm);
testNotifyEntityNeverMsgToEdgeServiceOneTime(alarm, alarm.getId(), tenantId, ActionType.ADDED); testNotifyEntityNever(alarm.getId(), alarm);
resetTokens(); resetTokens();

26
application/src/test/java/org/thingsboard/server/controller/ImageControllerTest.java

@ -130,13 +130,14 @@ public class ImageControllerTest extends AbstractControllerTest {
checkPngImageDescriptor(imageInfo.getDescriptor(ImageDescriptor.class)); checkPngImageDescriptor(imageInfo.getDescriptor(ImageDescriptor.class));
String newFilename = "my_jpeg_image.png"; String newFilename = "my_jpeg_image.png";
imageInfo = uploadImage(HttpMethod.PUT, "/api/images/tenant/" + filename, newFilename, "image/jpeg", JPEG_IMAGE); TbResourceInfo newImageInfo = uploadImage(HttpMethod.PUT, "/api/images/tenant/" + filename, newFilename, "image/jpeg", JPEG_IMAGE);
assertThat(imageInfo.getTitle()).isEqualTo(filename); assertThat(newImageInfo.getTitle()).isEqualTo(filename);
assertThat(imageInfo.getResourceKey()).isEqualTo(filename); assertThat(newImageInfo.getResourceKey()).isEqualTo(filename);
assertThat(imageInfo.getFileName()).isEqualTo(newFilename); assertThat(newImageInfo.getFileName()).isEqualTo(newFilename);
assertThat(newImageInfo.getPublicResourceKey()).isEqualTo(imageInfo.getPublicResourceKey());
ImageDescriptor imageDescriptor = imageInfo.getDescriptor(ImageDescriptor.class); ImageDescriptor imageDescriptor = newImageInfo.getDescriptor(ImageDescriptor.class);
checkJpegImageDescriptor(imageDescriptor); checkJpegImageDescriptor(imageDescriptor);
assertThat(downloadImage("tenant", filename)).containsExactly(JPEG_IMAGE); assertThat(downloadImage("tenant", filename)).containsExactly(JPEG_IMAGE);
@ -154,12 +155,15 @@ public class ImageControllerTest extends AbstractControllerTest {
assertThat(imageInfo.getFileName()).isEqualTo(filename); assertThat(imageInfo.getFileName()).isEqualTo(filename);
String newTitle = "My PNG image"; String newTitle = "My PNG image";
imageInfo.setTitle(newTitle); TbResourceInfo newImageInfo = new TbResourceInfo(imageInfo);
imageInfo.setDescriptor(JacksonUtil.newObjectNode()); newImageInfo.setTitle(newTitle);
imageInfo = doPut("/api/images/tenant/" + filename + "/info", imageInfo, TbResourceInfo.class); newImageInfo.setDescriptor(JacksonUtil.newObjectNode());
newImageInfo = doPut("/api/images/tenant/" + filename + "/info", newImageInfo, TbResourceInfo.class);
assertThat(imageInfo.getTitle()).isEqualTo(newTitle);
assertThat(imageInfo.getDescriptor(ImageDescriptor.class)).isEqualTo(imageDescriptor); assertThat(newImageInfo.getTitle()).isEqualTo(newTitle);
assertThat(newImageInfo.getDescriptor(ImageDescriptor.class)).isEqualTo(imageDescriptor);
assertThat(newImageInfo.getResourceKey()).isEqualTo(imageInfo.getResourceKey());
assertThat(newImageInfo.getPublicResourceKey()).isEqualTo(newImageInfo.getPublicResourceKey());
} }
@Test @Test

53
application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java

@ -40,6 +40,7 @@ import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantInfo; import org.thingsboard.server.common.data.TenantInfo;
import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.exception.TenantNotFoundException;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.msg.TbMsgType;
@ -64,6 +65,7 @@ import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.TbQueueAdmin; import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.QueueKey;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -71,7 +73,6 @@ import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Random;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
@ -80,6 +81,7 @@ import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat; 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.awaitility.Awaitility.await;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.argThat;
@ -700,6 +702,45 @@ public class TenantControllerTest extends AbstractControllerTest {
}); });
} }
@Test
public void whenTenantIsDeleted_thenDeleteQueues() throws Exception {
loginSysAdmin();
TenantProfile tenantProfile = new TenantProfile();
tenantProfile.setName("Test profile");
TenantProfileData tenantProfileData = new TenantProfileData();
tenantProfileData.setConfiguration(new DefaultTenantProfileConfiguration());
tenantProfile.setProfileData(tenantProfileData);
tenantProfile.setIsolatedTbRuleEngine(true);
addQueueConfig(tenantProfile, MAIN_QUEUE_NAME);
tenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class);
createDifferentTenant();
loginSysAdmin();
savedDifferentTenant.setTenantProfileId(tenantProfile.getId());
savedDifferentTenant = doPost("/api/tenant", savedDifferentTenant, Tenant.class);
TenantId tenantId = differentTenantId;
await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> {
assertThat(partitionService.getMyPartitions(new QueueKey(ServiceType.TB_RULE_ENGINE, tenantId))).isNotNull();
});
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, tenantId, tenantId);
assertThat(tpi.getTenantId()).hasValue(tenantId);
TbMsg tbMsg = publishTbMsg(tenantId, tpi);
await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> {
verify(actorContext).tell(argThat(msg -> {
return msg instanceof QueueToRuleEngineMsg && ((QueueToRuleEngineMsg) msg).getMsg().getId().equals(tbMsg.getId());
}));
});
deleteDifferentTenant();
await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> {
assertThat(partitionService.getMyPartitions(new QueueKey(ServiceType.TB_RULE_ENGINE, tenantId))).isNull();
assertThatThrownBy(() -> partitionService.resolve(ServiceType.TB_RULE_ENGINE, tenantId, tenantId))
.isInstanceOf(TenantNotFoundException.class);
verify(queueAdmin).deleteTopic(eq(tpi.getFullTopicName()));
});
}
private TbMsg publishTbMsg(TenantId tenantId, TopicPartitionInfo tpi) { private TbMsg publishTbMsg(TenantId tenantId, TopicPartitionInfo tpi) {
TbMsg tbMsg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, tenantId, TbMsgMetaData.EMPTY, "{\"test\":1}"); TbMsg tbMsg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, tenantId, TbMsgMetaData.EMPTY, "{\"test\":1}");
TransportProtos.ToRuleEngineMsg msg = TransportProtos.ToRuleEngineMsg.newBuilder() TransportProtos.ToRuleEngineMsg msg = TransportProtos.ToRuleEngineMsg.newBuilder()
@ -759,7 +800,7 @@ public class TenantControllerTest extends AbstractControllerTest {
queueConfiguration.setName(queueName); queueConfiguration.setName(queueName);
queueConfiguration.setTopic(topic); queueConfiguration.setTopic(topic);
queueConfiguration.setPollInterval(25); queueConfiguration.setPollInterval(25);
queueConfiguration.setPartitions(1 + new Random().nextInt(99)); queueConfiguration.setPartitions(12);
queueConfiguration.setConsumerPerPartition(true); queueConfiguration.setConsumerPerPartition(true);
queueConfiguration.setPackProcessingTimeout(2000); queueConfiguration.setPackProcessingTimeout(2000);
SubmitStrategy submitStrategy = new SubmitStrategy(); SubmitStrategy submitStrategy = new SubmitStrategy();
@ -799,20 +840,20 @@ public class TenantControllerTest extends AbstractControllerTest {
ArgumentMatcher<Tenant> matcherTenant = cntTime == 1 ? argument -> argument.equals(tenant) : ArgumentMatcher<Tenant> matcherTenant = cntTime == 1 ? argument -> argument.equals(tenant) :
argument -> argument.getClass().equals(Tenant.class); argument -> argument.getClass().equals(Tenant.class);
if (ComponentLifecycleEvent.DELETED.equals(event)) { if (ComponentLifecycleEvent.DELETED.equals(event)) {
Mockito.verify(tbClusterService, times( cntTime)).onTenantDelete(Mockito.argThat(matcherTenant), Mockito.verify(tbClusterService, times(cntTime)).onTenantDelete(Mockito.argThat(matcherTenant),
Mockito.isNull()); Mockito.isNull());
} else { } else {
Mockito.verify(tbClusterService, times( cntTime)).onTenantChange(Mockito.argThat(matcherTenant), Mockito.verify(tbClusterService, times(cntTime)).onTenantChange(Mockito.argThat(matcherTenant),
Mockito.isNull()); Mockito.isNull());
} }
TenantId tenantId = cntTime == 1 ? tenant.getId() : (TenantId) createEntityId_NULL_UUID(tenant); TenantId tenantId = cntTime == 1 ? tenant.getId() : (TenantId) createEntityId_NULL_UUID(tenant);
testBroadcastEntityStateChangeEventTime(tenantId, tenantId, cntTime); testBroadcastEntityStateChangeEventTime(tenantId, tenantId, cntTime);
Mockito.reset(tbClusterService); Mockito.reset(tbClusterService);
} }
private void testBroadcastEntityStateChangeEventNeverTenant() { private void testBroadcastEntityStateChangeEventNeverTenant() {
Mockito.verify(tbClusterService, never()).onTenantChange(Mockito.any(Tenant.class), Mockito.verify(tbClusterService, never()).onTenantChange(Mockito.any(Tenant.class),
Mockito.isNull()); Mockito.isNull());
testBroadcastEntityStateChangeEventNever(createEntityId_NULL_UUID(new Tenant())); testBroadcastEntityStateChangeEventNever(createEntityId_NULL_UUID(new Tenant()));
Mockito.reset(tbClusterService); Mockito.reset(tbClusterService);
} }

128
application/src/test/java/org/thingsboard/server/edge/AlarmEdgeTest.java

@ -15,21 +15,28 @@
*/ */
package org.thingsboard.server.edge; package org.thingsboard.server.edge;
import com.datastax.oss.driver.api.core.uuid.Uuids;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.node.TextNode;
import com.google.protobuf.AbstractMessage; import com.google.protobuf.AbstractMessage;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.alarm.AlarmCommentInfo;
import org.thingsboard.server.common.data.alarm.AlarmCommentType;
import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.id.AlarmCommentId;
import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.gen.edge.v1.AlarmCommentUpdateMsg;
import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg; import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.gen.edge.v1.UplinkMsg; import org.thingsboard.server.gen.edge.v1.UplinkMsg;
@ -84,26 +91,18 @@ public class AlarmEdgeTest extends AbstractEdgeTest {
alarm.setOriginator(device.getId()); alarm.setOriginator(device.getId());
alarm.setType("alarm"); alarm.setType("alarm");
alarm.setSeverity(AlarmSeverity.CRITICAL); alarm.setSeverity(AlarmSeverity.CRITICAL);
edgeImitator.expectMessageAmount(1);
Alarm savedAlarm = doPost("/api/alarm", alarm, Alarm.class); Alarm savedAlarm = doPost("/api/alarm", alarm, Alarm.class);
Assert.assertTrue(edgeImitator.waitForMessages()); edgeImitator.ignoreType(AlarmCommentUpdateMsg.class);
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof AlarmUpdateMsg);
AlarmUpdateMsg alarmUpdateMsg = (AlarmUpdateMsg) latestMessage;
Alarm alarmMsg = JacksonUtil.fromString(alarmUpdateMsg.getEntity(), Alarm.class, true);
Assert.assertNotNull(alarmMsg);
Assert.assertEquals(savedAlarm, alarmMsg);
Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, alarmUpdateMsg.getMsgType());
// ack alarm // ack alarm
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
doPost("/api/alarm/" + savedAlarm.getUuidId() + "/ack"); doPost("/api/alarm/" + savedAlarm.getUuidId() + "/ack");
Assert.assertTrue(edgeImitator.waitForMessages()); Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage(); AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof AlarmUpdateMsg); Assert.assertTrue(latestMessage instanceof AlarmUpdateMsg);
alarmUpdateMsg = (AlarmUpdateMsg) latestMessage; AlarmUpdateMsg alarmUpdateMsg = (AlarmUpdateMsg) latestMessage;
Assert.assertEquals(UpdateMsgType.ALARM_ACK_RPC_MESSAGE, alarmUpdateMsg.getMsgType()); Assert.assertEquals(UpdateMsgType.ALARM_ACK_RPC_MESSAGE, alarmUpdateMsg.getMsgType());
alarmMsg = JacksonUtil.fromString(alarmUpdateMsg.getEntity(), Alarm.class, true); Alarm alarmMsg = JacksonUtil.fromString(alarmUpdateMsg.getEntity(), Alarm.class, true);
Assert.assertNotNull(alarmMsg); Assert.assertNotNull(alarmMsg);
Assert.assertEquals(savedAlarm.getType(), alarmMsg.getType()); Assert.assertEquals(savedAlarm.getType(), alarmMsg.getType());
Assert.assertEquals(savedAlarm.getName(), alarmMsg.getName()); Assert.assertEquals(savedAlarm.getName(), alarmMsg.getName());
@ -137,6 +136,99 @@ public class AlarmEdgeTest extends AbstractEdgeTest {
Assert.assertEquals(savedAlarm.getType(), alarmMsg.getType()); Assert.assertEquals(savedAlarm.getType(), alarmMsg.getType());
Assert.assertEquals(savedAlarm.getName(), alarmMsg.getName()); Assert.assertEquals(savedAlarm.getName(), alarmMsg.getName());
Assert.assertEquals(AlarmStatus.CLEARED_ACK, alarmMsg.getStatus()); Assert.assertEquals(AlarmStatus.CLEARED_ACK, alarmMsg.getStatus());
edgeImitator.allowIgnoredTypes();
}
@Test
public void testSendAlarmCommentToCloud() throws Exception {
Device device = saveDeviceOnCloudAndVerifyDeliveryToEdge();
Alarm alarm = buildAlarmForUplinkMsg(device.getId());
UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder();
AlarmUpdateMsg.Builder alarmUpdateMgBuilder = AlarmUpdateMsg.newBuilder();
alarmUpdateMgBuilder.setIdMSB(alarm.getUuidId().getMostSignificantBits());
alarmUpdateMgBuilder.setIdLSB(alarm.getUuidId().getLeastSignificantBits());
alarmUpdateMgBuilder.setEntity(JacksonUtil.toString(alarm));
testAutoGeneratedCodeByProtobuf(alarmUpdateMgBuilder);
uplinkMsgBuilder.addAlarmUpdateMsg(alarmUpdateMgBuilder.build());
testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder);
edgeImitator.expectResponsesAmount(1);
edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());
Assert.assertTrue(edgeImitator.waitForResponses());
AlarmComment alarmComment = buildAlarmCommentForUplinkMsg(alarm.getId());
uplinkMsgBuilder = UplinkMsg.newBuilder();
AlarmCommentUpdateMsg.Builder alarmCommentUpdateMgBuilder = AlarmCommentUpdateMsg.newBuilder();
alarmCommentUpdateMgBuilder.setEntity(JacksonUtil.toString(alarmComment));
alarmCommentUpdateMgBuilder.setMsgType(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE);
testAutoGeneratedCodeByProtobuf(alarmCommentUpdateMgBuilder);
uplinkMsgBuilder.addAlarmCommentUpdateMsg(alarmCommentUpdateMgBuilder.build());
testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder);
edgeImitator.expectResponsesAmount(1);
edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());
Assert.assertTrue(edgeImitator.waitForResponses());
PageData<AlarmCommentInfo> pageData = doGetTyped("/api/alarm/" + alarmComment.getAlarmId().getId() + "/comment" + "?page=0&pageSize=1", new TypeReference<>() {});
Assert.assertNotNull("Found pageData is null", pageData);
Assert.assertNotEquals("Expected alarms are not found!", 0, pageData.getTotalElements());
Assert.assertNotNull(pageData.getData().get(0));
AlarmCommentInfo alarmInfo = pageData.getData().get(0);
Assert.assertEquals(alarm.getId(), alarmInfo.getAlarmId());
Assert.assertEquals(alarmComment.getAlarmId(), alarmInfo.getAlarmId());
}
@Test
public void testAlarmComments() throws Exception {
Device device = findDeviceByName("Edge Device 1");
Alarm alarm = new Alarm();
alarm.setOriginator(device.getId());
alarm.setType("alarm");
alarm.setSeverity(AlarmSeverity.MINOR);
Alarm savedAlarm = doPost("/api/alarm", alarm, Alarm.class);
// create alarm comment
edgeImitator.expectMessageAmount(1);
AlarmComment alarmComment = new AlarmComment();
alarmComment.setComment(new TextNode("Test"));
alarmComment.setAlarmId(savedAlarm.getId());
alarmComment = doPost("/api/alarm/" + savedAlarm.getUuidId() + "/comment", alarmComment, AlarmComment.class);
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
AlarmCommentUpdateMsg alarmCommentUpdateMsg = (AlarmCommentUpdateMsg) latestMessage;
Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, alarmCommentUpdateMsg.getMsgType());
AlarmComment alarmCommentMsg = JacksonUtil.fromString(alarmCommentUpdateMsg.getEntity(), AlarmComment.class, true);
Assert.assertNotNull(alarmCommentMsg);
Assert.assertEquals(alarmComment, alarmCommentMsg);
// update alarm comment
edgeImitator.expectMessageAmount(1);
alarmComment.setComment(JacksonUtil.newObjectNode().set("text", new TextNode("Updated comment")));
alarmComment = doPost("/api/alarm/" + savedAlarm.getUuidId() + "/comment", alarmComment, AlarmComment.class);
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
alarmCommentUpdateMsg = (AlarmCommentUpdateMsg) latestMessage;
Assert.assertEquals(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, alarmCommentUpdateMsg.getMsgType());
alarmCommentMsg = JacksonUtil.fromString(alarmCommentUpdateMsg.getEntity(), AlarmComment.class, true);
Assert.assertNotNull(alarmCommentMsg);
Assert.assertEquals(alarmComment, alarmCommentMsg);
// delete alarm
edgeImitator.expectMessageAmount(1);
doDelete("/api/alarm/" + savedAlarm.getUuidId() + "/comment/" + alarmComment.getUuidId())
.andExpect(status().isOk());
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
alarmCommentUpdateMsg = (AlarmCommentUpdateMsg) latestMessage;
Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, alarmCommentUpdateMsg.getMsgType());
alarmCommentMsg = JacksonUtil.fromString(alarmCommentUpdateMsg.getEntity(), AlarmComment.class, true);
Assert.assertNotNull(alarmCommentMsg);
} }
private Alarm buildAlarmForUplinkMsg(DeviceId deviceId) { private Alarm buildAlarmForUplinkMsg(DeviceId deviceId) {
@ -148,4 +240,16 @@ public class AlarmEdgeTest extends AbstractEdgeTest {
alarm.setSeverity(AlarmSeverity.CRITICAL); alarm.setSeverity(AlarmSeverity.CRITICAL);
return alarm; return alarm;
} }
private AlarmComment buildAlarmCommentForUplinkMsg(AlarmId alarmId) {
UUID uuid = Uuids.timeBased();
AlarmComment alarmComment = new AlarmComment();
alarmComment.setAlarmId(alarmId);
alarmComment.setType(AlarmCommentType.OTHER);
alarmComment.setUserId(tenantAdminUserId);
alarmComment.setId(new AlarmCommentId(uuid));
alarmComment.setComment(new TextNode("AlarmComment"));
alarmComment.setCreatedTime(Uuids.unixTimestamp(uuid));
return alarmComment;
}
} }

6
application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java

@ -27,6 +27,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
import org.thingsboard.edge.rpc.EdgeGrpcClient; import org.thingsboard.edge.rpc.EdgeGrpcClient;
import org.thingsboard.edge.rpc.EdgeRpcClient; import org.thingsboard.edge.rpc.EdgeRpcClient;
import org.thingsboard.server.gen.edge.v1.AdminSettingsUpdateMsg; import org.thingsboard.server.gen.edge.v1.AdminSettingsUpdateMsg;
import org.thingsboard.server.gen.edge.v1.AlarmCommentUpdateMsg;
import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg; import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg;
import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg; import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg; import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg;
@ -231,6 +232,11 @@ public class EdgeImitator {
result.add(saveDownlinkMsg(alarmUpdateMsg)); result.add(saveDownlinkMsg(alarmUpdateMsg));
} }
} }
if (downlinkMsg.getAlarmCommentUpdateMsgCount() > 0) {
for (AlarmCommentUpdateMsg alarmCommentUpdateMsg : downlinkMsg.getAlarmCommentUpdateMsgList()) {
result.add(saveDownlinkMsg(alarmCommentUpdateMsg));
}
}
if (downlinkMsg.getEntityDataCount() > 0) { if (downlinkMsg.getEntityDataCount() > 0) {
for (EntityDataProto entityData : downlinkMsg.getEntityDataList()) { for (EntityDataProto entityData : downlinkMsg.getEntityDataList()) {
if (randomFailuresOnTimeseriesDownlink) { if (randomFailuresOnTimeseriesDownlink) {

4
application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessorTest.java

@ -32,6 +32,7 @@ import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.alarm.AlarmCommentService;
import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.asset.AssetProfileService; import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.asset.AssetService;
@ -157,6 +158,9 @@ public abstract class BaseEdgeProcessorTest {
@MockBean @MockBean
protected AlarmService alarmService; protected AlarmService alarmService;
@MockBean
protected AlarmCommentService alarmCommentService;
@MockBean @MockBean
protected DeviceService deviceService; protected DeviceService deviceService;

4
application/src/test/java/org/thingsboard/server/service/queue/ruleengine/TbRuleEngineQueueConsumerManagerTest.java

@ -447,7 +447,7 @@ public class TbRuleEngineQueueConsumerManagerTest {
verifyMsgProcessed(consumer1.testMsg); verifyMsgProcessed(consumer1.testMsg);
verifyMsgProcessed(consumer2.testMsg); verifyMsgProcessed(consumer2.testMsg);
consumerManager.delete(); consumerManager.delete(true);
await().atMost(2, TimeUnit.SECONDS) await().atMost(2, TimeUnit.SECONDS)
.untilAsserted(() -> { .untilAsserted(() -> {
@ -488,7 +488,7 @@ public class TbRuleEngineQueueConsumerManagerTest {
verifySubscribedAndLaunched(consumer, partitions); verifySubscribedAndLaunched(consumer, partitions);
verifyMsgProcessed(consumer.testMsg); verifyMsgProcessed(consumer.testMsg);
consumerManager.delete(); consumerManager.delete(true);
await().atMost(2, TimeUnit.SECONDS) await().atMost(2, TimeUnit.SECONDS)
.untilAsserted(() -> { .untilAsserted(() -> {

2
application/src/test/resources/logback-test.xml

@ -38,8 +38,6 @@
<!-- Device actor message processor debug for the test scope --> <!-- Device actor message processor debug for the test scope -->
<!-- <logger name="org.thingsboard.server.actors.device.DeviceActorMessageProcessor" level="DEBUG" />--> <!-- <logger name="org.thingsboard.server.actors.device.DeviceActorMessageProcessor" level="DEBUG" />-->
<logger name="org.thingsboard.server.service.subscription" level="TRACE"/>
<root level="WARN"> <root level="WARN">
<appender-ref ref="console"/> <appender-ref ref="console"/>
</root> </root>

1
common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmCommentService.java

@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
public interface AlarmCommentService { public interface AlarmCommentService {
AlarmComment createOrUpdateAlarmComment(TenantId tenantId, AlarmComment alarmComment); AlarmComment createOrUpdateAlarmComment(TenantId tenantId, AlarmComment alarmComment);
AlarmComment saveAlarmComment(TenantId tenantId, AlarmComment alarmComment); AlarmComment saveAlarmComment(TenantId tenantId, AlarmComment alarmComment);

13
common/data/src/main/java/org/thingsboard/server/common/data/EdgeUtils.java

@ -18,6 +18,7 @@ package org.thingsboard.server.common.data;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.base.Throwables; import com.google.common.base.Throwables;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.edge.EdgeEventType;
@ -32,6 +33,7 @@ import java.util.concurrent.ThreadLocalRandom;
public final class EdgeUtils { public final class EdgeUtils {
private static final EnumMap<EntityType, EdgeEventType> entityTypeEdgeEventTypeEnumMap; private static final EnumMap<EntityType, EdgeEventType> entityTypeEdgeEventTypeEnumMap;
private static final EnumMap<ActionType, EdgeEventActionType> actionTypeEdgeEventActionTypeEnumMap;
static { static {
entityTypeEdgeEventTypeEnumMap = new EnumMap<>(EntityType.class); entityTypeEdgeEventTypeEnumMap = new EnumMap<>(EntityType.class);
@ -40,6 +42,13 @@ public final class EdgeUtils {
entityTypeEdgeEventTypeEnumMap.put(edgeEventType.getEntityType(), edgeEventType); entityTypeEdgeEventTypeEnumMap.put(edgeEventType.getEntityType(), edgeEventType);
} }
} }
actionTypeEdgeEventActionTypeEnumMap = new EnumMap<>(ActionType.class);
for (EdgeEventActionType edgeEventActionType : EdgeEventActionType.values()) {
if (edgeEventActionType.getActionType() != null) {
actionTypeEdgeEventActionTypeEnumMap.put(edgeEventActionType.getActionType(), edgeEventActionType);
}
}
} }
private static final int STACK_TRACE_LIMIT = 10; private static final int STACK_TRACE_LIMIT = 10;
@ -54,6 +63,10 @@ public final class EdgeUtils {
return entityTypeEdgeEventTypeEnumMap.get(entityType); return entityTypeEdgeEventTypeEnumMap.get(entityType);
} }
public static EdgeEventActionType getEdgeEventActionTypeByActionType(ActionType actionType) {
return actionTypeEdgeEventActionTypeEnumMap.get(actionType);
}
public static EdgeEvent constructEdgeEvent(TenantId tenantId, public static EdgeEvent constructEdgeEvent(TenantId tenantId,
EdgeId edgeId, EdgeId edgeId,
EdgeEventType type, EdgeEventType type,

57
common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventActionType.java

@ -15,26 +15,39 @@
*/ */
package org.thingsboard.server.common.data.edge; package org.thingsboard.server.common.data.edge;
import lombok.Getter;
import org.thingsboard.server.common.data.audit.ActionType;
@Getter
public enum EdgeEventActionType { public enum EdgeEventActionType {
ADDED, ADDED(ActionType.ADDED),
DELETED, UPDATED(ActionType.UPDATED),
UPDATED, DELETED(ActionType.DELETED),
POST_ATTRIBUTES, POST_ATTRIBUTES(null),
ATTRIBUTES_UPDATED, ATTRIBUTES_UPDATED(ActionType.ATTRIBUTES_UPDATED),
ATTRIBUTES_DELETED, ATTRIBUTES_DELETED(ActionType.ATTRIBUTES_DELETED),
TIMESERIES_UPDATED, TIMESERIES_UPDATED(ActionType.TIMESERIES_UPDATED),
CREDENTIALS_UPDATED, CREDENTIALS_UPDATED(ActionType.CREDENTIALS_UPDATED),
ASSIGNED_TO_CUSTOMER, ASSIGNED_TO_CUSTOMER(ActionType.ASSIGNED_TO_CUSTOMER),
UNASSIGNED_FROM_CUSTOMER, UNASSIGNED_FROM_CUSTOMER(ActionType.UNASSIGNED_FROM_CUSTOMER),
RELATION_ADD_OR_UPDATE, RELATION_ADD_OR_UPDATE(ActionType.RELATION_ADD_OR_UPDATE),
RELATION_DELETED, RELATION_DELETED(ActionType.RELATION_DELETED),
RPC_CALL, RPC_CALL(ActionType.RPC_CALL),
ALARM_ACK, ALARM_ACK(ActionType.ALARM_ACK),
ALARM_CLEAR, ALARM_CLEAR(ActionType.ALARM_CLEAR),
ALARM_ASSIGNED, ALARM_ASSIGNED(ActionType.ALARM_ASSIGNED),
ALARM_UNASSIGNED, ALARM_UNASSIGNED(ActionType.ALARM_UNASSIGNED),
ASSIGNED_TO_EDGE, ADDED_COMMENT(ActionType.ADDED_COMMENT),
UNASSIGNED_FROM_EDGE, UPDATED_COMMENT(ActionType.UPDATED_COMMENT),
CREDENTIALS_REQUEST, DELETED_COMMENT(ActionType.DELETED_COMMENT),
ENTITY_MERGE_REQUEST // deprecated ASSIGNED_TO_EDGE(ActionType.ASSIGNED_TO_EDGE),
} UNASSIGNED_FROM_EDGE(ActionType.UNASSIGNED_FROM_EDGE),
CREDENTIALS_REQUEST(null),
ENTITY_MERGE_REQUEST(null); // deprecated
private final ActionType actionType;
EdgeEventActionType(ActionType actionType) {
this.actionType = actionType;
}
}

1
common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventType.java

@ -27,6 +27,7 @@ public enum EdgeEventType {
ASSET_PROFILE(true, EntityType.ASSET_PROFILE), ASSET_PROFILE(true, EntityType.ASSET_PROFILE),
ENTITY_VIEW(false, EntityType.ENTITY_VIEW), ENTITY_VIEW(false, EntityType.ENTITY_VIEW),
ALARM(false, EntityType.ALARM), ALARM(false, EntityType.ALARM),
ALARM_COMMENT(false, null),
RULE_CHAIN(false, EntityType.RULE_CHAIN), RULE_CHAIN(false, EntityType.RULE_CHAIN),
RULE_CHAIN_METADATA(false, null), RULE_CHAIN_METADATA(false, null),
EDGE(false, EntityType.EDGE), EDGE(false, EntityType.EDGE),

5
common/data/src/main/java/org/thingsboard/server/common/data/page/BasePageDataIterable.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.common.data.page; package org.thingsboard.server.common.data.page;
import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
@ -64,8 +65,8 @@ public abstract class BasePageDataIterable<T> implements Iterable<T>, Iterator<T
private void fetch(PageLink link) { private void fetch(PageLink link) {
PageData<T> pageData = fetchPageData(link); PageData<T> pageData = fetchPageData(link);
currentIdx = 0; currentIdx = 0;
currentItems = pageData.getData(); currentItems = pageData != null ? pageData.getData() : new ArrayList<>();
hasNextPack = pageData.hasNext(); hasNextPack = pageData != null && pageData.hasNext();
nextPackLink = link.nextPageLink(); nextPackLink = link.nextPageLink();
} }

7
common/edge-api/src/main/proto/edge.proto

@ -325,6 +325,11 @@ message AlarmUpdateMsg {
string entity = 18; string entity = 18;
} }
message AlarmCommentUpdateMsg {
UpdateMsgType msgType = 1;
string entity = 2;
}
message CustomerUpdateMsg { message CustomerUpdateMsg {
UpdateMsgType msgType = 1; UpdateMsgType msgType = 1;
int64 idMSB = 2; int64 idMSB = 2;
@ -618,6 +623,7 @@ message UplinkMsg {
repeated AssetProfileUpdateMsg assetProfileUpdateMsg = 19; repeated AssetProfileUpdateMsg assetProfileUpdateMsg = 19;
repeated DeviceProfileUpdateMsg deviceProfileUpdateMsg = 20; repeated DeviceProfileUpdateMsg deviceProfileUpdateMsg = 20;
repeated ResourceUpdateMsg resourceUpdateMsg = 21; repeated ResourceUpdateMsg resourceUpdateMsg = 21;
repeated AlarmCommentUpdateMsg alarmCommentUpdateMsg = 22;
} }
message UplinkResponseMsg { message UplinkResponseMsg {
@ -661,5 +667,6 @@ message DownlinkMsg {
repeated TenantUpdateMsg tenantUpdateMsg = 26; repeated TenantUpdateMsg tenantUpdateMsg = 26;
repeated TenantProfileUpdateMsg tenantProfileUpdateMsg = 27; repeated TenantProfileUpdateMsg tenantProfileUpdateMsg = 27;
repeated ResourceUpdateMsg resourceUpdateMsg = 28; repeated ResourceUpdateMsg resourceUpdateMsg = 28;
repeated AlarmCommentUpdateMsg alarmCommentUpdateMsg = 29;
} }

32
common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java

@ -35,7 +35,6 @@ import org.thingsboard.server.queue.discovery.event.ServiceListChangedEvent;
import org.thingsboard.server.queue.util.AfterStartUp; import org.thingsboard.server.queue.util.AfterStartUp;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@ -190,14 +189,25 @@ public class HashPartitionService implements PartitionService {
myPartitions.remove(queueKey); myPartitions.remove(queueKey);
partitionTopicsMap.remove(queueKey); partitionTopicsMap.remove(queueKey);
partitionSizesMap.remove(queueKey); partitionSizesMap.remove(queueKey);
//TODO: remove after merging tb entity services evictTenantInfo(tenantId);
removeTenant(tenantId);
if (serviceInfoProvider.isService(ServiceType.TB_RULE_ENGINE)) { if (serviceInfoProvider.isService(ServiceType.TB_RULE_ENGINE)) {
publishPartitionChangeEvent(ServiceType.TB_RULE_ENGINE, Map.of(queueKey, Collections.emptySet())); publishPartitionChangeEvent(ServiceType.TB_RULE_ENGINE, Map.of(queueKey, Collections.emptySet()));
} }
} }
@Override
public void removeTenant(TenantId tenantId) {
List<QueueKey> queueKeys = partitionSizesMap.keySet().stream()
.filter(queueKey -> tenantId.equals(queueKey.getTenantId()))
.collect(Collectors.toList());
queueKeys.forEach(queueKey -> {
myPartitions.remove(queueKey);
partitionTopicsMap.remove(queueKey);
partitionSizesMap.remove(queueKey);
});
evictTenantInfo(tenantId);
}
@Override @Override
public boolean isManagedByCurrentService(TenantId tenantId) { public boolean isManagedByCurrentService(TenantId tenantId) {
Set<UUID> assignedTenantProfiles = serviceInfoProvider.getAssignedTenantProfiles(); Set<UUID> assignedTenantProfiles = serviceInfoProvider.getAssignedTenantProfiles();
@ -258,6 +268,7 @@ public class HashPartitionService implements PartitionService {
@Override @Override
public synchronized void recalculatePartitions(ServiceInfo currentService, List<ServiceInfo> otherServices) { public synchronized void recalculatePartitions(ServiceInfo currentService, List<ServiceInfo> otherServices) {
log.info("Recalculating partitions");
tbTransportServicesByType.clear(); tbTransportServicesByType.clear();
responsibleServices.clear(); responsibleServices.clear();
logServiceInfo(currentService); logServiceInfo(currentService);
@ -274,9 +285,14 @@ public class HashPartitionService implements PartitionService {
final ConcurrentMap<QueueKey, List<Integer>> newPartitions = new ConcurrentHashMap<>(); final ConcurrentMap<QueueKey, List<Integer>> newPartitions = new ConcurrentHashMap<>();
partitionSizesMap.forEach((queueKey, size) -> { partitionSizesMap.forEach((queueKey, size) -> {
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
ServiceInfo serviceInfo = resolveByPartitionIdx(queueServicesMap.get(queueKey), queueKey, i); try {
if (currentService.equals(serviceInfo)) { ServiceInfo serviceInfo = resolveByPartitionIdx(queueServicesMap.get(queueKey), queueKey, i);
newPartitions.computeIfAbsent(queueKey, key -> new ArrayList<>()).add(i); log.trace("Server responsible for {}[{}] - {}", queueKey, i, serviceInfo != null ? serviceInfo.getServiceId() : "none");
if (currentService.equals(serviceInfo)) {
newPartitions.computeIfAbsent(queueKey, key -> new ArrayList<>()).add(i);
}
} catch (Exception e) {
log.warn("Failed to resolve server responsible for {}[{}]", queueKey, i, e);
} }
} }
}); });
@ -399,7 +415,7 @@ public class HashPartitionService implements PartitionService {
} }
@Override @Override
public void removeTenant(TenantId tenantId) { public void evictTenantInfo(TenantId tenantId) {
tenantRoutingInfoMap.remove(tenantId); tenantRoutingInfoMap.remove(tenantId);
} }

4
common/queue/src/main/java/org/thingsboard/server/queue/discovery/PartitionService.java

@ -59,7 +59,7 @@ public interface PartitionService {
int resolvePartitionIndex(UUID entityId, int partitions); int resolvePartitionIndex(UUID entityId, int partitions);
void removeTenant(TenantId tenantId); void evictTenantInfo(TenantId tenantId);
int countTransportsByType(String type); int countTransportsByType(String type);
@ -67,6 +67,8 @@ public interface PartitionService {
void removeQueue(TransportProtos.QueueDeleteMsg queueDeleteMsg); void removeQueue(TransportProtos.QueueDeleteMsg queueDeleteMsg);
void removeTenant(TenantId tenantId);
boolean isManagedByCurrentService(TenantId tenantId); boolean isManagedByCurrentService(TenantId tenantId);
} }

23
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java

@ -28,10 +28,9 @@ import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
@Slf4j @Slf4j
public abstract class AbstractActivityManager<Key, Metadata> implements ActivityManager<Key> { public abstract class AbstractActivityManager<Key, Metadata> implements ActivityManager<Key, Metadata> {
private final ConcurrentMap<Key, ActivityStateWrapper> states = new ConcurrentHashMap<>(); private final ConcurrentMap<Key, ActivityStateWrapper> states = new ConcurrentHashMap<>();
@ -54,8 +53,6 @@ public abstract class AbstractActivityManager<Key, Metadata> implements Activity
protected abstract long getReportingPeriodMillis(); protected abstract long getReportingPeriodMillis();
protected abstract ActivityState<Metadata> createNewState(Key key);
protected abstract ActivityStrategy getStrategy(); protected abstract ActivityStrategy getStrategy();
protected abstract ActivityState<Metadata> updateState(Key key, ActivityState<Metadata> state); protected abstract ActivityState<Metadata> updateState(Key key, ActivityState<Metadata> state);
@ -67,7 +64,7 @@ public abstract class AbstractActivityManager<Key, Metadata> implements Activity
protected abstract void reportActivity(Key key, Metadata metadata, long timeToReport, ActivityReportCallback<Key> callback); protected abstract void reportActivity(Key key, Metadata metadata, long timeToReport, ActivityReportCallback<Key> callback);
@Override @Override
public void onActivity(Key key, long newLastRecordedTime) { public void onActivity(Key key, Metadata metadata, long newLastRecordedTime) {
if (key == null) { if (key == null) {
log.error("Failed to process activity event: provided activity key is null."); log.error("Failed to process activity event: provided activity key is null.");
return; return;
@ -77,36 +74,28 @@ public abstract class AbstractActivityManager<Key, Metadata> implements Activity
var shouldReport = new AtomicBoolean(false); var shouldReport = new AtomicBoolean(false);
var lastRecordedTime = new AtomicLong(); var lastRecordedTime = new AtomicLong();
var lastReportedTime = new AtomicLong(); var lastReportedTime = new AtomicLong();
var metadata = new AtomicReference<Metadata>();
var activityStateWrapper = states.compute(key, (__, stateWrapper) -> { states.compute(key, (__, stateWrapper) -> {
if (stateWrapper == null) { if (stateWrapper == null) {
var newState = createNewState(key); ActivityState<Metadata> newState = new ActivityState<>();
if (newState == null) {
return null;
}
stateWrapper = new ActivityStateWrapper(); stateWrapper = new ActivityStateWrapper();
stateWrapper.setState(newState); stateWrapper.setState(newState);
stateWrapper.setStrategy(getStrategy()); stateWrapper.setStrategy(getStrategy());
} }
var state = stateWrapper.getState(); var state = stateWrapper.getState();
state.setMetadata(metadata);
if (state.getLastRecordedTime() < newLastRecordedTime) { if (state.getLastRecordedTime() < newLastRecordedTime) {
state.setLastRecordedTime(newLastRecordedTime); state.setLastRecordedTime(newLastRecordedTime);
} }
shouldReport.set(stateWrapper.getStrategy().onActivity()); shouldReport.set(stateWrapper.getStrategy().onActivity());
lastRecordedTime.set(state.getLastRecordedTime()); lastRecordedTime.set(state.getLastRecordedTime());
lastReportedTime.set(stateWrapper.getLastReportedTime()); lastReportedTime.set(stateWrapper.getLastReportedTime());
metadata.set(state.getMetadata());
return stateWrapper; return stateWrapper;
}); });
if (activityStateWrapper == null) {
return;
}
if (shouldReport.get() && lastReportedTime.get() < lastRecordedTime.get()) { if (shouldReport.get() && lastReportedTime.get() < lastRecordedTime.get()) {
log.debug("Going to report first activity event for key: [{}].", key); log.debug("Going to report first activity event for key: [{}].", key);
reportActivity(key, metadata.get(), lastRecordedTime.get(), new ActivityReportCallback<>() { reportActivity(key, metadata, lastRecordedTime.get(), new ActivityReportCallback<>() {
@Override @Override
public void onSuccess(Key key, long reportedTime) { public void onSuccess(Key key, long reportedTime) {
updateLastReportedTime(key, reportedTime); updateLastReportedTime(key, reportedTime);

4
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java

@ -15,9 +15,9 @@
*/ */
package org.thingsboard.server.common.transport.activity; package org.thingsboard.server.common.transport.activity;
public interface ActivityManager<Key> { public interface ActivityManager<Key, Metadata> {
void onActivity(Key key, long activityTimeMillis); void onActivity(Key key, Metadata metadata, long activityTimeMillis);
void onReportingPeriodEnd(); void onReportingPeriodEnd();

8
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java

@ -772,7 +772,7 @@ public class DefaultTransportService extends TransportActivityManager implements
} }
private void recordActivityInternal(TransportProtos.SessionInfoProto sessionInfo) { private void recordActivityInternal(TransportProtos.SessionInfoProto sessionInfo) {
onActivity(toSessionId(sessionInfo), getCurrentTimeMillis()); onActivity(toSessionId(sessionInfo), sessionInfo, getCurrentTimeMillis());
} }
@Override @Override
@ -931,8 +931,8 @@ public class DefaultTransportService extends TransportActivityManager implements
Optional<Tenant> profileOpt = dataDecodingEncodingService.decode(msg.getData().toByteArray()); Optional<Tenant> profileOpt = dataDecodingEncodingService.decode(msg.getData().toByteArray());
if (profileOpt.isPresent()) { if (profileOpt.isPresent()) {
Tenant tenant = profileOpt.get(); Tenant tenant = profileOpt.get();
partitionService.removeTenant(tenant.getId());
boolean updated = tenantProfileCache.put(tenant.getId(), tenant.getTenantProfileId()); boolean updated = tenantProfileCache.put(tenant.getId(), tenant.getTenantProfileId());
partitionService.evictTenantInfo(tenant.getId());
if (updated) { if (updated) {
rateLimitService.update(tenant.getId()); rateLimitService.update(tenant.getId());
} }
@ -957,7 +957,9 @@ public class DefaultTransportService extends TransportActivityManager implements
} else if (EntityType.TENANT_PROFILE.equals(entityType)) { } else if (EntityType.TENANT_PROFILE.equals(entityType)) {
tenantProfileCache.remove(new TenantProfileId(entityUuid)); tenantProfileCache.remove(new TenantProfileId(entityUuid));
} else if (EntityType.TENANT.equals(entityType)) { } else if (EntityType.TENANT.equals(entityType)) {
rateLimitService.remove(TenantId.fromUUID(entityUuid)); TenantId tenantId = TenantId.fromUUID(entityUuid);
rateLimitService.remove(tenantId);
partitionService.removeTenant(tenantId);
} else if (EntityType.DEVICE.equals(entityType)) { } else if (EntityType.DEVICE.equals(entityType)) {
rateLimitService.remove(new DeviceId(entityUuid)); rateLimitService.remove(new DeviceId(entityUuid));
onDeviceDeleted(new DeviceId(entityUuid)); onDeviceDeleted(new DeviceId(entityUuid));

11
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TransportActivityManager.java

@ -57,17 +57,6 @@ public abstract class TransportActivityManager extends AbstractActivityManager<U
return sessionReportTimeout; return sessionReportTimeout;
} }
@Override
protected ActivityState<TransportProtos.SessionInfoProto> createNewState(UUID sessionId) {
SessionMetaData session = sessions.get(sessionId);
if (session == null) {
return null;
}
ActivityState<TransportProtos.SessionInfoProto> state = new ActivityState<>();
state.setMetadata(session.getSessionInfo());
return state;
}
@Override @Override
protected ActivityStrategy getStrategy() { protected ActivityStrategy getStrategy() {
return reportingStrategyType.toStrategy(); return reportingStrategyType.toStrategy();

53
common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/service/TransportActivityManagerTest.java

@ -40,6 +40,8 @@ import java.util.concurrent.ConcurrentMap;
import java.util.stream.Stream; import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
@ -63,6 +65,34 @@ public class TransportActivityManagerTest {
ReflectionTestUtils.setField(transportServiceMock, "sessions", sessions); ReflectionTestUtils.setField(transportServiceMock, "sessions", sessions);
} }
@Test
void givenFirstActivityForAlreadyRemovedSessionAndFirstEventReportingStrategy_whenOnActivity_thenShouldRecordActivityAndReport() {
// GIVEN
ConcurrentMap<UUID, Object> states = new ConcurrentHashMap<>();
ReflectionTestUtils.setField(transportServiceMock, "states", states);
var strategyMock = mock(ActivityStrategy.class);
when(transportServiceMock.getStrategy()).thenReturn(strategyMock);
when(strategyMock.onActivity()).thenReturn(true);
long activityTime = 123L;
var sessionInfo = TransportProtos.SessionInfoProto.newBuilder()
.setSessionIdMSB(SESSION_ID.getMostSignificantBits())
.setSessionIdLSB(SESSION_ID.getLeastSignificantBits())
.build();
doCallRealMethod().when(transportServiceMock).getLastRecordedTime(SESSION_ID);
doCallRealMethod().when(transportServiceMock).onActivity(SESSION_ID, sessionInfo, activityTime);
// WHEN
transportServiceMock.onActivity(SESSION_ID, sessionInfo, activityTime);
// THEN
assertThat(states).containsKey(SESSION_ID);
assertThat(transportServiceMock.getLastRecordedTime(SESSION_ID)).isEqualTo(activityTime);
verify(transportServiceMock).reportActivity(eq(SESSION_ID), eq(sessionInfo), eq(activityTime), any(ActivityReportCallback.class));
}
@Test @Test
void givenKeyAndTimeToReportAndSessionExists_whenReportingActivity_thenShouldReportActivityWithSubscriptionsAndSessionInfoFromSession() { void givenKeyAndTimeToReportAndSessionExists_whenReportingActivity_thenShouldReportActivityWithSubscriptionsAndSessionInfoFromSession() {
// GIVEN // GIVEN
@ -175,28 +205,7 @@ public class TransportActivityManagerTest {
transportServiceMock.recordActivity(sessionInfo); transportServiceMock.recordActivity(sessionInfo);
// THEN // THEN
verify(transportServiceMock).onActivity(SESSION_ID, expectedTime); verify(transportServiceMock).onActivity(SESSION_ID, sessionInfo, expectedTime);
}
@Test
void givenKey_whenCreatingNewState_thenShouldCorrectlyCreateNewEmptyState() {
// GIVEN
TransportProtos.SessionInfoProto sessionInfo = TransportProtos.SessionInfoProto.newBuilder()
.setSessionIdMSB(SESSION_ID.getMostSignificantBits())
.setSessionIdLSB(SESSION_ID.getLeastSignificantBits())
.build();
sessions.put(SESSION_ID, new SessionMetaData(sessionInfo, TransportProtos.SessionType.ASYNC, null));
when(transportServiceMock.createNewState(SESSION_ID)).thenCallRealMethod();
ActivityState<TransportProtos.SessionInfoProto> expectedState = new ActivityState<>();
expectedState.setMetadata(sessionInfo);
// WHEN
ActivityState<TransportProtos.SessionInfoProto> actualState = transportServiceMock.createNewState(SESSION_ID);
// THEN
assertThat(actualState).isEqualTo(expectedState);
} }
@ParameterizedTest @ParameterizedTest

21
dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmCommentService.java

@ -31,6 +31,8 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent;
import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent;
import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.DataValidator;
import java.util.UUID; import java.util.UUID;
@ -50,18 +52,28 @@ public class BaseAlarmCommentService extends AbstractEntityService implements Al
@Override @Override
public AlarmComment createOrUpdateAlarmComment(TenantId tenantId, AlarmComment alarmComment) { public AlarmComment createOrUpdateAlarmComment(TenantId tenantId, AlarmComment alarmComment) {
alarmCommentDataValidator.validate(alarmComment, c -> tenantId); alarmCommentDataValidator.validate(alarmComment, c -> tenantId);
if (alarmComment.getId() == null) { boolean isCreated = alarmComment.getId() == null;
return createAlarmComment(tenantId, alarmComment); AlarmComment result;
if (isCreated) {
result = createAlarmComment(tenantId, alarmComment);
} else { } else {
return updateAlarmComment(tenantId, alarmComment); result = updateAlarmComment(tenantId, alarmComment);
}
if (result != null) {
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(tenantId).entity(result)
.entityId(result.getAlarmId()).created(isCreated).build());
} }
return result;
} }
@Override @Override
public AlarmComment saveAlarmComment(TenantId tenantId, AlarmComment alarmComment) { public AlarmComment saveAlarmComment(TenantId tenantId, AlarmComment alarmComment) {
log.debug("Deleting Alarm Comment: {}", alarmComment); log.debug("Deleting Alarm Comment: {}", alarmComment);
alarmCommentDataValidator.validate(alarmComment, c -> tenantId); alarmCommentDataValidator.validate(alarmComment, c -> tenantId);
return alarmCommentDao.save(tenantId, alarmComment); AlarmComment result = alarmCommentDao.save(tenantId, alarmComment);
eventPublisher.publishEvent(DeleteEntityEvent.builder().tenantId(tenantId).entity(result)
.entityId(result.getAlarmId()).build());
return result;
} }
@Override @Override
@ -112,5 +124,4 @@ public class BaseAlarmCommentService extends AbstractEntityService implements Al
} }
return null; return null;
} }
} }

2
dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java

@ -135,7 +135,7 @@ public class BaseAlarmService extends AbstractCachedEntityService<TenantId, Page
} }
if (result.getAlarm() != null) { if (result.getAlarm() != null) {
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(result.getAlarm().getTenantId()) eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(result.getAlarm().getTenantId())
.entityId(result.getAlarm().getId()).added(true).build()); .entityId(result.getAlarm().getId()).entity(result).created(true).build());
publishEvictEvent(new AlarmTypesCacheEvictEvent(request.getTenantId())); publishEvictEvent(new AlarmTypesCacheEvictEvent(request.getTenantId()));
} }
return withPropagated(result); return withPropagated(result);

2
dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java

@ -153,7 +153,7 @@ public class AssetProfileServiceImpl extends AbstractCachedEntityService<AssetPr
publishEvictEvent(new AssetProfileEvictEvent(savedAssetProfile.getTenantId(), savedAssetProfile.getName(), publishEvictEvent(new AssetProfileEvictEvent(savedAssetProfile.getTenantId(), savedAssetProfile.getName(),
oldAssetProfile != null ? oldAssetProfile.getName() : null, savedAssetProfile.getId(), savedAssetProfile.isDefault())); oldAssetProfile != null ? oldAssetProfile.getName() : null, savedAssetProfile.getId(), savedAssetProfile.isDefault()));
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(savedAssetProfile.getTenantId()).entityId(savedAssetProfile.getId()) eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(savedAssetProfile.getTenantId()).entityId(savedAssetProfile.getId())
.added(oldAssetProfile == null).build()); .created(oldAssetProfile == null).build());
} catch (Exception t) { } catch (Exception t) {
handleEvictEvent(new AssetProfileEvictEvent(assetProfile.getTenantId(), assetProfile.getName(), handleEvictEvent(new AssetProfileEvictEvent(assetProfile.getTenantId(), assetProfile.getName(),
oldAssetProfile != null ? oldAssetProfile.getName() : null, null, assetProfile.isDefault())); oldAssetProfile != null ? oldAssetProfile.getName() : null, null, assetProfile.isDefault()));

4
dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java

@ -172,7 +172,7 @@ public class BaseAssetService extends AbstractCachedEntityService<AssetCacheKey,
savedAsset = assetDao.saveAndFlush(asset.getTenantId(), asset); savedAsset = assetDao.saveAndFlush(asset.getTenantId(), asset);
publishEvictEvent(evictEvent); publishEvictEvent(evictEvent);
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(savedAsset.getTenantId()) eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(savedAsset.getTenantId())
.entityId(savedAsset.getId()).added(asset.getId() == null).build()); .entityId(savedAsset.getId()).created(asset.getId() == null).build());
if (asset.getId() == null) { if (asset.getId() == null) {
countService.publishCountEntityEvictEvent(savedAsset.getTenantId(), EntityType.ASSET); countService.publishCountEntityEvictEvent(savedAsset.getTenantId(), EntityType.ASSET);
} }
@ -215,7 +215,7 @@ public class BaseAssetService extends AbstractCachedEntityService<AssetCacheKey,
private void deleteAsset(TenantId tenantId, Asset asset) { private void deleteAsset(TenantId tenantId, Asset asset) {
log.trace("Executing deleteAsset [{}]", asset.getId()); log.trace("Executing deleteAsset [{}]", asset.getId());
relationService.deleteEntityRelations(tenantId, asset.getAssetProfileId()); relationService.deleteEntityRelations(tenantId, asset.getId());
assetDao.removeById(tenantId, asset.getUuidId()); assetDao.removeById(tenantId, asset.getUuidId());

2
dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java

@ -113,7 +113,7 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom
countService.publishCountEntityEvictEvent(savedCustomer.getTenantId(), EntityType.CUSTOMER); countService.publishCountEntityEvictEvent(savedCustomer.getTenantId(), EntityType.CUSTOMER);
} }
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(savedCustomer.getTenantId()) eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(savedCustomer.getTenantId())
.entityId(savedCustomer.getId()).added(customer.getId() == null).build()); .entityId(savedCustomer.getId()).created(customer.getId() == null).build());
return savedCustomer; return savedCustomer;
} catch (Exception e) { } catch (Exception e) {
checkConstraintViolation(e, "customer_external_id_unq_key", "Customer with such external id already exists!"); checkConstraintViolation(e, "customer_external_id_unq_key", "Customer with such external id already exists!");

2
dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java

@ -161,7 +161,7 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
var saved = dashboardDao.save(dashboard.getTenantId(), dashboard); var saved = dashboardDao.save(dashboard.getTenantId(), dashboard);
publishEvictEvent(new DashboardTitleEvictEvent(saved.getId())); publishEvictEvent(new DashboardTitleEvictEvent(saved.getId()));
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(saved.getTenantId()) eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(saved.getTenantId())
.entityId(saved.getId()).added(dashboard.getId() == null).build()); .entityId(saved.getId()).created(dashboard.getId() == null).build());
if (dashboard.getId() == null) { if (dashboard.getId() == null) {
countService.publishCountEntityEvictEvent(saved.getTenantId(), EntityType.DASHBOARD); countService.publishCountEntityEvictEvent(saved.getTenantId(), EntityType.DASHBOARD);
} }

2
dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java

@ -191,7 +191,7 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService<Device
oldDeviceProfile != null ? oldDeviceProfile.getName() : null, savedDeviceProfile.getId(), savedDeviceProfile.isDefault(), oldDeviceProfile != null ? oldDeviceProfile.getName() : null, savedDeviceProfile.getId(), savedDeviceProfile.isDefault(),
oldDeviceProfile != null ? oldDeviceProfile.getProvisionDeviceKey() : null)); oldDeviceProfile != null ? oldDeviceProfile.getProvisionDeviceKey() : null));
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(savedDeviceProfile.getTenantId()) eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(savedDeviceProfile.getTenantId())
.entityId(savedDeviceProfile.getId()).added(oldDeviceProfile == null).build()); .entityId(savedDeviceProfile.getId()).created(oldDeviceProfile == null).build());
} catch (Exception t) { } catch (Exception t) {
handleEvictEvent(new DeviceProfileEvictEvent(deviceProfile.getTenantId(), deviceProfile.getName(), handleEvictEvent(new DeviceProfileEvictEvent(deviceProfile.getTenantId(), deviceProfile.getName(),
oldDeviceProfile != null ? oldDeviceProfile.getName() : null, null, deviceProfile.isDefault(), oldDeviceProfile != null ? oldDeviceProfile.getName() : null, null, deviceProfile.isDefault(),

3
dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java

@ -79,7 +79,6 @@ import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.PaginatedRemover;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
@ -239,7 +238,7 @@ public class DeviceServiceImpl extends AbstractCachedEntityService<DeviceCacheKe
countService.publishCountEntityEvictEvent(result.getTenantId(), EntityType.DEVICE); countService.publishCountEntityEvictEvent(result.getTenantId(), EntityType.DEVICE);
} }
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(result.getTenantId()) eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(result.getTenantId())
.entityId(result.getId()).added(device.getId() == null).build()); .entityId(result.getId()).created(device.getId() == null).build());
return result; return result;
} catch (Exception t) { } catch (Exception t) {
handleEvictEvent(deviceCacheEvictEvent); handleEvictEvent(deviceCacheEvictEvent);

2
dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java

@ -123,7 +123,7 @@ public class EntityViewServiceImpl extends AbstractCachedEntityService<EntityVie
EntityView saved = entityViewDao.save(entityView.getTenantId(), entityView); EntityView saved = entityViewDao.save(entityView.getTenantId(), entityView);
publishEvictEvent(new EntityViewEvictEvent(saved.getTenantId(), saved.getId(), saved.getEntityId(), old != null ? old.getEntityId() : null, saved.getName(), old != null ? old.getName() : null)); publishEvictEvent(new EntityViewEvictEvent(saved.getTenantId(), saved.getId(), saved.getEntityId(), old != null ? old.getEntityId() : null, saved.getName(), old != null ? old.getName() : null));
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(saved.getTenantId()) eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(saved.getTenantId())
.entityId(saved.getId()).added(entityView.getId() == null).build()); .entityId(saved.getId()).created(entityView.getId() == null).build());
return saved; return saved;
} catch (Exception t) { } catch (Exception t) {
checkConstraintViolation(t, checkConstraintViolation(t,

2
dao/src/main/java/org/thingsboard/server/dao/eventsourcing/SaveEntityEvent.java

@ -27,5 +27,5 @@ public class SaveEntityEvent<T> {
private final T entity; private final T entity;
private final T oldEntity; private final T oldEntity;
private final EntityId entityId; private final EntityId entityId;
private final Boolean added; private final Boolean created;
} }

4
dao/src/main/java/org/thingsboard/server/dao/ota/BaseOtaPackageService.java

@ -84,7 +84,7 @@ public class BaseOtaPackageService extends AbstractCachedEntityService<OtaPackag
publishEvictEvent(new OtaPackageCacheEvictEvent(otaPackageId)); publishEvictEvent(new OtaPackageCacheEvictEvent(otaPackageId));
} }
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(result.getTenantId()).entity(result) eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(result.getTenantId()).entity(result)
.entityId(result.getId()).added(otaPackageId == null).build()); .entityId(result.getId()).created(otaPackageId == null).build());
return result; return result;
} catch (Exception t) { } catch (Exception t) {
if (otaPackageId != null) { if (otaPackageId != null) {
@ -110,7 +110,7 @@ public class BaseOtaPackageService extends AbstractCachedEntityService<OtaPackag
publishEvictEvent(new OtaPackageCacheEvictEvent(otaPackageId)); publishEvictEvent(new OtaPackageCacheEvictEvent(otaPackageId));
} }
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(result.getTenantId()) eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(result.getTenantId())
.entityId(result.getId()).added(otaPackageId == null).build()); .entityId(result.getId()).created(otaPackageId == null).build());
return result; return result;
} catch (Exception t) { } catch (Exception t) {
if (otaPackageId != null) { if (otaPackageId != null) {

2
dao/src/main/java/org/thingsboard/server/dao/queue/BaseQueueService.java

@ -66,7 +66,7 @@ public class BaseQueueService extends AbstractEntityService implements QueueServ
queueValidator.validate(queue, Queue::getTenantId); queueValidator.validate(queue, Queue::getTenantId);
Queue savedQueue = queueDao.save(queue.getTenantId(), queue); Queue savedQueue = queueDao.save(queue.getTenantId(), queue);
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(savedQueue.getTenantId()) eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(savedQueue.getTenantId())
.entityId(savedQueue.getId()).added(queue.getId() == null).build()); .entityId(savedQueue.getId()).created(queue.getId() == null).build());
return savedQueue; return savedQueue;
} }

7
dao/src/main/java/org/thingsboard/server/dao/resource/BaseImageService.java

@ -150,12 +150,9 @@ public class BaseImageService extends BaseResourceService implements ImageServic
image.setDescriptorValue(descriptor); image.setDescriptorValue(descriptor);
image.setPreview(result.getRight()); image.setPreview(result.getRight());
if (StringUtils.isEmpty(image.getPublicResourceKey())) { if (StringUtils.isEmpty(image.getPublicResourceKey()) || (image.getId() == null &&
resourceInfoDao.existsByPublicResourceKey(ResourceType.IMAGE, image.getPublicResourceKey()))) {
image.setPublicResourceKey(generatePublicResourceKey()); image.setPublicResourceKey(generatePublicResourceKey());
} else {
if (resourceInfoDao.existsByPublicResourceKey(ResourceType.IMAGE, image.getPublicResourceKey())) {
image.setPublicResourceKey(generatePublicResourceKey());
}
} }
log.debug("[{}] Creating image {} ('{}')", image.getTenantId(), image.getResourceKey(), image.getName()); log.debug("[{}] Creating image {} ('{}')", image.getTenantId(), image.getResourceKey(), image.getName());
return new TbResourceInfo(doSaveResource(image)); return new TbResourceInfo(doSaveResource(image));

2
dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java

@ -90,7 +90,7 @@ public class BaseResourceService extends AbstractCachedEntityService<ResourceInf
} }
publishEvictEvent(new ResourceInfoEvictEvent(tenantId, resource.getId())); publishEvictEvent(new ResourceInfoEvictEvent(tenantId, resource.getId()));
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(saved.getTenantId()) eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(saved.getTenantId())
.entityId(saved.getId()).added(resource.getId() == null).build()); .entityId(saved.getId()).created(resource.getId() == null).build());
return saved; return saved;
} catch (Exception t) { } catch (Exception t) {
publishEvictEvent(new ResourceInfoEvictEvent(tenantId, resource.getId())); publishEvictEvent(new ResourceInfoEvictEvent(tenantId, resource.getId()));

2
dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java

@ -116,7 +116,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
entityCountService.publishCountEntityEvictEvent(ruleChain.getTenantId(), EntityType.RULE_CHAIN); entityCountService.publishCountEntityEvictEvent(ruleChain.getTenantId(), EntityType.RULE_CHAIN);
} }
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(savedRuleChain.getTenantId()) eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(savedRuleChain.getTenantId())
.entity(savedRuleChain).entityId(savedRuleChain.getId()).added(ruleChain.getId() == null).build()); .entity(savedRuleChain).entityId(savedRuleChain.getId()).created(ruleChain.getId() == null).build());
return savedRuleChain; return savedRuleChain;
} catch (Exception e) { } catch (Exception e) {
checkConstraintViolation(e, "rule_chain_external_id_unq_key", "Rule Chain with such external id already exists!"); checkConstraintViolation(e, "rule_chain_external_id_unq_key", "Rule Chain with such external id already exists!");

2
dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java

@ -95,7 +95,7 @@ public class TenantProfileServiceImpl extends AbstractCachedEntityService<Tenant
savedTenantProfile = tenantProfileDao.save(tenantId, tenantProfile); savedTenantProfile = tenantProfileDao.save(tenantId, tenantProfile);
publishEvictEvent(new TenantProfileEvictEvent(savedTenantProfile.getId(), savedTenantProfile.isDefault())); publishEvictEvent(new TenantProfileEvictEvent(savedTenantProfile.getId(), savedTenantProfile.isDefault()));
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(tenantId) eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(tenantId)
.entityId(savedTenantProfile.getId()).added(tenantProfile.getId() == null).build()); .entityId(savedTenantProfile.getId()).created(tenantProfile.getId() == null).build());
} catch (Exception t) { } catch (Exception t) {
handleEvictEvent(new TenantProfileEvictEvent(null, tenantProfile.isDefault())); handleEvictEvent(new TenantProfileEvictEvent(null, tenantProfile.isDefault()));
ConstraintViolationException e = extractConstraintViolationException(t).orElse(null); ConstraintViolationException e = extractConstraintViolationException(t).orElse(null);

2
dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java

@ -197,7 +197,7 @@ public class TenantServiceImpl extends AbstractCachedEntityService<TenantId, Ten
boolean create = tenant.getId() == null; boolean create = tenant.getId() == null;
Tenant savedTenant = tenantDao.save(tenant.getId(), tenant); Tenant savedTenant = tenantDao.save(tenant.getId(), tenant);
publishEvictEvent(new TenantEvictEvent(savedTenant.getId(), create)); publishEvictEvent(new TenantEvictEvent(savedTenant.getId(), create));
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(savedTenant.getId()).entityId(savedTenant.getId()).added(create).build()); eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(savedTenant.getId()).entityId(savedTenant.getId()).created(create).build());
if (tenant.getId() == null) { if (tenant.getId() == null) {
deviceProfileService.createDefaultDeviceProfile(savedTenant.getId()); deviceProfileService.createDefaultDeviceProfile(savedTenant.getId());
assetProfileService.createDefaultAssetProfile(savedTenant.getId()); assetProfileService.createDefaultAssetProfile(savedTenant.getId());

2
dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java

@ -145,7 +145,7 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic
.entity(savedUser) .entity(savedUser)
.oldEntity(oldUser) .oldEntity(oldUser)
.entityId(savedUser.getId()) .entityId(savedUser.getId())
.added(user.getId() == null).build()); .created(user.getId() == null).build());
return savedUser; return savedUser;
} }

4
dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeServiceImpl.java

@ -97,7 +97,7 @@ public class WidgetTypeServiceImpl implements WidgetTypeService {
imageService.replaceBase64WithImageUrl(widgetTypeDetails); imageService.replaceBase64WithImageUrl(widgetTypeDetails);
WidgetTypeDetails result = widgetTypeDao.save(widgetTypeDetails.getTenantId(), widgetTypeDetails); WidgetTypeDetails result = widgetTypeDao.save(widgetTypeDetails.getTenantId(), widgetTypeDetails);
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(result.getTenantId()) eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(result.getTenantId())
.entityId(result.getId()).added(widgetTypeDetails.getId() == null).build()); .entityId(result.getId()).created(widgetTypeDetails.getId() == null).build());
return result; return result;
} catch (Exception t) { } catch (Exception t) {
AbstractCachedEntityService.checkConstraintViolation(t, AbstractCachedEntityService.checkConstraintViolation(t,
@ -209,7 +209,7 @@ public class WidgetTypeServiceImpl implements WidgetTypeService {
widgetTypeDao.saveWidgetsBundleWidget(widgetsBundleWidget); widgetTypeDao.saveWidgetsBundleWidget(widgetsBundleWidget);
} }
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(tenantId) eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(tenantId)
.entityId(widgetsBundleId).added(false).build()); .entityId(widgetsBundleId).created(false).build());
} }
@Override @Override

2
dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleServiceImpl.java

@ -78,7 +78,7 @@ public class WidgetsBundleServiceImpl implements WidgetsBundleService {
imageService.replaceBase64WithImageUrl(widgetsBundle, "bundle"); imageService.replaceBase64WithImageUrl(widgetsBundle, "bundle");
WidgetsBundle result = widgetsBundleDao.save(widgetsBundle.getTenantId(), widgetsBundle); WidgetsBundle result = widgetsBundleDao.save(widgetsBundle.getTenantId(), widgetsBundle);
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(result.getTenantId()) eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(result.getTenantId())
.entityId(result.getId()).added(widgetsBundle.getId() == null).build()); .entityId(result.getId()).created(widgetsBundle.getId() == null).build());
return result; return result;
} catch (Exception e) { } catch (Exception e) {
AbstractCachedEntityService.checkConstraintViolation(e, AbstractCachedEntityService.checkConstraintViolation(e,

13
dao/src/test/java/org/thingsboard/server/dao/service/AssetServiceTest.java

@ -23,8 +23,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition; import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
@ -32,21 +30,22 @@ import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetInfo; import org.thingsboard.server.common.data.asset.AssetInfo;
import org.thingsboard.server.common.data.asset.AssetProfile; import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.dao.asset.AssetDao; import org.thingsboard.server.dao.asset.AssetDao;
import org.thingsboard.server.dao.asset.AssetProfileService; import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.relation.RelationService;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.UUID;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
@ -60,6 +59,8 @@ public class AssetServiceTest extends AbstractServiceTest {
@Autowired @Autowired
CustomerService customerService; CustomerService customerService;
@Autowired @Autowired
RelationService relationService;
@Autowired
private AssetProfileService assetProfileService; private AssetProfileService assetProfileService;
@Autowired @Autowired
private PlatformTransactionManager platformTransactionManager; private PlatformTransactionManager platformTransactionManager;
@ -252,11 +253,15 @@ public class AssetServiceTest extends AbstractServiceTest {
asset.setName("My asset"); asset.setName("My asset");
asset.setType("default"); asset.setType("default");
Asset savedAsset = assetService.saveAsset(asset); Asset savedAsset = assetService.saveAsset(asset);
EntityRelation relation = new EntityRelation(tenantId, savedAsset.getId(), EntityRelation.CONTAINS_TYPE);
relationService.saveRelation(tenantId, relation);
Asset foundAsset = assetService.findAssetById(tenantId, savedAsset.getId()); Asset foundAsset = assetService.findAssetById(tenantId, savedAsset.getId());
Assert.assertNotNull(foundAsset); Assert.assertNotNull(foundAsset);
assetService.deleteAsset(tenantId, savedAsset.getId()); assetService.deleteAsset(tenantId, savedAsset.getId());
foundAsset = assetService.findAssetById(tenantId, savedAsset.getId()); foundAsset = assetService.findAssetById(tenantId, savedAsset.getId());
Assert.assertNull(foundAsset); Assert.assertNull(foundAsset);
Assert.assertTrue(relationService.findByTo(tenantId, savedAsset.getId(), RelationTypeGroup.COMMON).isEmpty());
} }
@Test @Test

4
monitoring/src/main/java/org/thingsboard/monitoring/config/MonitoringTarget.java

@ -23,6 +23,10 @@ public interface MonitoringTarget {
String getBaseUrl(); String getBaseUrl();
default String getQueue() {
return "Main";
}
boolean isCheckDomainIps(); boolean isCheckDomainIps();
} }

7
monitoring/src/main/java/org/thingsboard/monitoring/config/transport/TransportInfo.java

@ -22,10 +22,15 @@ public class TransportInfo {
private final TransportType transportType; private final TransportType transportType;
private final String baseUrl; private final String baseUrl;
private final String queue;
@Override @Override
public String toString() { public String toString() {
return String.format("%s transport (%s)", transportType, baseUrl); if (queue.equals("Main")) {
return String.format("*%s* (%s)", transportType.getName(), baseUrl);
} else {
return String.format("*%s* (%s) _%s_", transportType.getName(), baseUrl, queue);
}
} }
} }

6
monitoring/src/main/java/org/thingsboard/monitoring/config/transport/TransportMonitoringTarget.java

@ -16,6 +16,7 @@
package org.thingsboard.monitoring.config.transport; package org.thingsboard.monitoring.config.transport;
import lombok.Data; import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.thingsboard.monitoring.config.MonitoringTarget; import org.thingsboard.monitoring.config.MonitoringTarget;
import java.util.UUID; import java.util.UUID;
@ -25,6 +26,7 @@ public class TransportMonitoringTarget implements MonitoringTarget {
private String baseUrl; private String baseUrl;
private DeviceConfig device; // set manually during initialization private DeviceConfig device; // set manually during initialization
private String queue;
private boolean checkDomainIps; private boolean checkDomainIps;
@Override @Override
@ -32,4 +34,8 @@ public class TransportMonitoringTarget implements MonitoringTarget {
return device.getId(); return device.getId();
} }
public String getQueue() {
return StringUtils.defaultIfEmpty(queue, "Main");
}
} }

9
monitoring/src/main/java/org/thingsboard/monitoring/config/transport/TransportType.java

@ -27,11 +27,12 @@ import org.thingsboard.monitoring.service.transport.impl.MqttTransportHealthChec
@Getter @Getter
public enum TransportType { public enum TransportType {
MQTT(MqttTransportHealthChecker.class), MQTT("MQTT", MqttTransportHealthChecker.class),
COAP(CoapTransportHealthChecker.class), COAP("CoAP",CoapTransportHealthChecker.class),
HTTP(HttpTransportHealthChecker.class), HTTP("HTTP", HttpTransportHealthChecker.class),
LWM2M(Lwm2mTransportHealthChecker.class); LWM2M("LwM2M", Lwm2mTransportHealthChecker.class);
private final String name;
private final Class<? extends TransportHealthChecker<?>> serviceClass; private final Class<? extends TransportHealthChecker<?>> serviceClass;
} }

2
monitoring/src/main/java/org/thingsboard/monitoring/data/notification/HighLatencyNotification.java

@ -34,7 +34,7 @@ public class HighLatencyNotification implements Notification {
StringBuilder text = new StringBuilder(); StringBuilder text = new StringBuilder();
text.append("Some of the latencies are higher than ").append(thresholdMs).append(" ms:\n"); text.append("Some of the latencies are higher than ").append(thresholdMs).append(" ms:\n");
highLatencies.forEach(latency -> { highLatencies.forEach(latency -> {
text.append(String.format("[%s] %s\n", latency.getKey(), latency.getFormattedValue())); text.append(String.format("[%s] *%s*\n", latency.getKey(), latency.getFormattedValue()));
}); });
return text.toString(); return text.toString();
} }

2
monitoring/src/main/java/org/thingsboard/monitoring/data/notification/ServiceFailureNotification.java

@ -43,7 +43,7 @@ public class ServiceFailureNotification implements Notification {
if (errorMsg == null) { if (errorMsg == null) {
errorMsg = error.getClass().getSimpleName(); errorMsg = error.getClass().getSimpleName();
} }
return String.format("[%s] Failure: %s (number of subsequent failures: %s)", serviceKey, errorMsg, failuresCount); return String.format("%s - Failure: %s (number of subsequent failures: %s)", serviceKey, errorMsg, failuresCount);
} }
} }

2
monitoring/src/main/java/org/thingsboard/monitoring/data/notification/ServiceRecoveryNotification.java

@ -25,7 +25,7 @@ public class ServiceRecoveryNotification implements Notification {
@Override @Override
public String getText() { public String getText() {
return String.format("[%s] is OK", serviceKey); return String.format("%s is OK", serviceKey);
} }
} }

18
monitoring/src/main/java/org/thingsboard/monitoring/notification/NotificationService.java

@ -17,6 +17,8 @@ package org.thingsboard.monitoring.notification;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.monitoring.data.notification.Notification; import org.thingsboard.monitoring.data.notification.Notification;
import org.thingsboard.monitoring.notification.channels.NotificationChannel; import org.thingsboard.monitoring.notification.channels.NotificationChannel;
@ -24,7 +26,6 @@ import org.thingsboard.monitoring.notification.channels.NotificationChannel;
import java.util.List; import java.util.List;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.function.Consumer;
@Component @Component
@RequiredArgsConstructor @RequiredArgsConstructor
@ -34,15 +35,20 @@ public class NotificationService {
private final List<NotificationChannel> notificationChannels; private final List<NotificationChannel> notificationChannels;
private final ExecutorService notificationExecutor = Executors.newSingleThreadExecutor(); private final ExecutorService notificationExecutor = Executors.newSingleThreadExecutor();
public void sendNotification(Notification notification) { @Value("${monitoring.notifications.message_prefix}")
forEachNotificationChannel(notificationChannel -> notificationChannel.sendNotification(notification)); private String messagePrefix;
}
private void forEachNotificationChannel(Consumer<NotificationChannel> function) { public void sendNotification(Notification notification) {
String message;
if (StringUtils.isEmpty(messagePrefix)) {
message = notification.getText();
} else {
message = messagePrefix + System.lineSeparator() + notification.getText();
}
notificationChannels.forEach(notificationChannel -> { notificationChannels.forEach(notificationChannel -> {
notificationExecutor.submit(() -> { notificationExecutor.submit(() -> {
try { try {
function.accept(notificationChannel); notificationChannel.sendNotification(message);
} catch (Exception e) { } catch (Exception e) {
log.error("Failed to send notification to {}", notificationChannel.getClass().getSimpleName(), e); log.error("Failed to send notification to {}", notificationChannel.getClass().getSimpleName(), e);
} }

4
monitoring/src/main/java/org/thingsboard/monitoring/notification/channels/NotificationChannel.java

@ -15,10 +15,8 @@
*/ */
package org.thingsboard.monitoring.notification.channels; package org.thingsboard.monitoring.notification.channels;
import org.thingsboard.monitoring.data.notification.Notification;
public interface NotificationChannel { public interface NotificationChannel {
void sendNotification(Notification notification); void sendNotification(String message);
} }

11
monitoring/src/main/java/org/thingsboard/monitoring/notification/channels/impl/SlackNotificationChannel.java

@ -21,7 +21,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import org.thingsboard.monitoring.data.notification.Notification;
import org.thingsboard.monitoring.notification.channels.NotificationChannel; import org.thingsboard.monitoring.notification.channels.NotificationChannel;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
@ -29,11 +28,11 @@ import java.time.Duration;
import java.util.Map; import java.util.Map;
@Component @Component
@ConditionalOnProperty(value = "monitoring.notification_channels.slack.enabled", havingValue = "true") @ConditionalOnProperty(value = "monitoring.notifications.slack.enabled", havingValue = "true")
@Slf4j @Slf4j
public class SlackNotificationChannel implements NotificationChannel { public class SlackNotificationChannel implements NotificationChannel {
@Value("${monitoring.notification_channels.slack.webhook_url}") @Value("${monitoring.notifications.slack.webhook_url}")
private String webhookUrl; private String webhookUrl;
private RestTemplate restTemplate; private RestTemplate restTemplate;
@ -47,11 +46,7 @@ public class SlackNotificationChannel implements NotificationChannel {
} }
@Override @Override
public void sendNotification(Notification notification) { public void sendNotification(String message) {
sendNotification(notification.getText());
}
private void sendNotification(String message) {
restTemplate.postForObject(webhookUrl, Map.of("text", message), String.class); restTemplate.postForObject(webhookUrl, Map.of("text", message), String.class);
} }

96
monitoring/src/main/java/org/thingsboard/monitoring/service/transport/TransportHealthChecker.java

@ -29,6 +29,8 @@ import org.thingsboard.monitoring.service.BaseHealthChecker;
import org.thingsboard.monitoring.util.ResourceUtils; import org.thingsboard.monitoring.util.ResourceUtils;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileType;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MBootstrapClientCredentials; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MBootstrapClientCredentials;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials;
@ -38,6 +40,9 @@ import org.thingsboard.server.common.data.device.data.DefaultDeviceConfiguration
import org.thingsboard.server.common.data.device.data.DefaultDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.DefaultDeviceTransportConfiguration;
import org.thingsboard.server.common.data.device.data.DeviceData; import org.thingsboard.server.common.data.device.data.DeviceData;
import org.thingsboard.server.common.data.device.data.Lwm2mDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.Lwm2mDeviceTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.common.data.security.DeviceCredentialsType;
@ -45,27 +50,19 @@ import org.thingsboard.server.common.data.security.DeviceCredentialsType;
@Slf4j @Slf4j
public abstract class TransportHealthChecker<C extends TransportMonitoringConfig> extends BaseHealthChecker<C, TransportMonitoringTarget> { public abstract class TransportHealthChecker<C extends TransportMonitoringConfig> extends BaseHealthChecker<C, TransportMonitoringTarget> {
private static final String DEFAULT_DEVICE_NAME = "[Monitoring] %s transport (%s)";
private static final String DEFAULT_PROFILE_NAME = "[Monitoring] %s";
public TransportHealthChecker(C config, TransportMonitoringTarget target) { public TransportHealthChecker(C config, TransportMonitoringTarget target) {
super(config, target); super(config, target);
} }
@Override @Override
protected void initialize(TbClient tbClient) { protected void initialize(TbClient tbClient) {
String deviceName = String.format(DEFAULT_DEVICE_NAME, config.getTransportType(), target.getBaseUrl()); Device device = getOrCreateDevice(tbClient);
Device device = tbClient.getTenantDevice(deviceName)
.orElseGet(() -> {
log.info("Creating new device '{}'", deviceName);
return createDevice(config.getTransportType(), deviceName, tbClient);
});
DeviceCredentials credentials = tbClient.getDeviceCredentialsByDeviceId(device.getId()) DeviceCredentials credentials = tbClient.getDeviceCredentialsByDeviceId(device.getId())
.orElseThrow(() -> new IllegalArgumentException("No credentials found for device " + device.getId())); .orElseThrow(() -> new IllegalArgumentException("No credentials found for device " + device.getId()));
DeviceConfig deviceConfig = new DeviceConfig(); DeviceConfig deviceConfig = new DeviceConfig();
deviceConfig.setId(device.getId().toString()); deviceConfig.setId(device.getId().toString());
deviceConfig.setName(deviceName); deviceConfig.setName(device.getName());
deviceConfig.setCredentials(credentials); deviceConfig.setCredentials(credentials);
target.setDevice(deviceConfig); target.setDevice(deviceConfig);
} }
@ -77,51 +74,43 @@ public abstract class TransportHealthChecker<C extends TransportMonitoringConfig
@Override @Override
protected Object getInfo() { protected Object getInfo() {
return new TransportInfo(getTransportType(), target.getBaseUrl()); return new TransportInfo(getTransportType(), target.getBaseUrl(), target.getQueue());
} }
@Override @Override
protected String getKey() { protected String getKey() {
return getTransportType().name().toLowerCase() + "Transport"; return getTransportType().name().toLowerCase() + (target.getQueue().equals("Main") ? "" : target.getQueue()) + "Transport";
} }
protected abstract TransportType getTransportType(); protected abstract TransportType getTransportType();
private Device createDevice(TransportType transportType, String name, TbClient tbClient) { private Device getOrCreateDevice(TbClient tbClient) {
Device device = new Device(); TransportType transportType = config.getTransportType();
device.setName(name); String deviceName = String.format("%s (%s) - %s", transportType.getName(), target.getQueue(), target.getBaseUrl());
Device device = tbClient.getTenantDevice(deviceName).orElse(null);
if (device != null) {
return device;
}
log.info("Creating new device '{}'", deviceName);
device = new Device();
device.setName(deviceName);
DeviceCredentials credentials = new DeviceCredentials(); DeviceCredentials credentials = new DeviceCredentials();
credentials.setCredentialsId(RandomStringUtils.randomAlphabetic(20)); credentials.setCredentialsId(RandomStringUtils.randomAlphabetic(20));
DeviceData deviceData = new DeviceData(); DeviceData deviceData = new DeviceData();
deviceData.setConfiguration(new DefaultDeviceConfiguration()); deviceData.setConfiguration(new DefaultDeviceConfiguration());
DeviceProfile deviceProfile = getOrCreateDeviceProfile(tbClient);
device.setType(deviceProfile.getName());
device.setDeviceProfileId(deviceProfile.getId());
if (transportType != TransportType.LWM2M) { if (transportType != TransportType.LWM2M) {
device.setType("default");
deviceData.setTransportConfiguration(new DefaultDeviceTransportConfiguration()); deviceData.setTransportConfiguration(new DefaultDeviceTransportConfiguration());
credentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN); credentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN);
} else { } else {
tbClient.getResources(new PageLink(1, 0, "lwm2m monitoring")).getData()
.stream().findFirst()
.orElseGet(() -> {
TbResource newResource = ResourceUtils.getResource("lwm2m/resource.json", TbResource.class);
log.info("Creating LwM2M resource");
return tbClient.saveResource(newResource);
});
String profileName = String.format(DEFAULT_PROFILE_NAME, transportType);
DeviceProfile profile = tbClient.getDeviceProfiles(new PageLink(1, 0, profileName)).getData()
.stream().findFirst()
.orElseGet(() -> {
DeviceProfile newProfile = ResourceUtils.getResource("lwm2m/device_profile.json", DeviceProfile.class);
newProfile.setName(profileName);
log.info("Creating LwM2M device profile");
return tbClient.saveDeviceProfile(newProfile);
});
device.setType(profileName);
device.setDeviceProfileId(profile.getId());
deviceData.setTransportConfiguration(new Lwm2mDeviceTransportConfiguration()); deviceData.setTransportConfiguration(new Lwm2mDeviceTransportConfiguration());
credentials.setCredentialsType(DeviceCredentialsType.LWM2M_CREDENTIALS); credentials.setCredentialsType(DeviceCredentialsType.LWM2M_CREDENTIALS);
LwM2MDeviceCredentials lwm2mCreds = new LwM2MDeviceCredentials(); LwM2MDeviceCredentials lwm2mCreds = new LwM2MDeviceCredentials();
NoSecClientCredential client = new NoSecClientCredential(); NoSecClientCredential client = new NoSecClientCredential();
@ -133,7 +122,42 @@ public abstract class TransportHealthChecker<C extends TransportMonitoringConfig
lwm2mCreds.setBootstrap(bootstrap); lwm2mCreds.setBootstrap(bootstrap);
credentials.setCredentialsValue(JacksonUtil.toString(lwm2mCreds)); credentials.setCredentialsValue(JacksonUtil.toString(lwm2mCreds));
} }
return tbClient.saveDeviceWithCredentials(device, credentials).get(); return tbClient.saveDeviceWithCredentials(device, credentials).get();
} }
private DeviceProfile getOrCreateDeviceProfile(TbClient tbClient) {
TransportType transportType = config.getTransportType();
String profileName = String.format("%s (%s)", transportType.getName(), target.getQueue());
DeviceProfile deviceProfile = tbClient.getDeviceProfiles(new PageLink(1, 0, profileName)).getData()
.stream().findFirst().orElse(null);
if (deviceProfile != null) {
return deviceProfile;
}
log.info("Creating new device profile '{}'", profileName);
if (transportType != TransportType.LWM2M) {
deviceProfile = new DeviceProfile();
deviceProfile.setType(DeviceProfileType.DEFAULT);
deviceProfile.setTransportType(DeviceTransportType.DEFAULT);
DeviceProfileData profileData = new DeviceProfileData();
profileData.setConfiguration(new DefaultDeviceProfileConfiguration());
profileData.setTransportConfiguration(new DefaultDeviceProfileTransportConfiguration());
deviceProfile.setProfileData(profileData);
} else {
tbClient.getResources(new PageLink(1, 0, "lwm2m monitoring")).getData()
.stream().findFirst()
.orElseGet(() -> {
TbResource newResource = ResourceUtils.getResource("lwm2m/resource.json", TbResource.class);
log.info("Creating LwM2M resource");
return tbClient.saveResource(newResource);
});
deviceProfile = ResourceUtils.getResource("lwm2m/device_profile.json", DeviceProfile.class);
}
deviceProfile.setName(profileName);
deviceProfile.setDefaultQueueName(target.getQueue());
return tbClient.saveDeviceProfile(deviceProfile);
}
} }

13
monitoring/src/main/resources/tb-monitoring.yml

@ -38,7 +38,7 @@ monitoring:
check_timeout_ms: '${CHECK_TIMEOUT_MS:5000}' check_timeout_ms: '${CHECK_TIMEOUT_MS:5000}'
# Failures threshold for notifying # Failures threshold for notifying
failures_threshold: '${FAILURES_THRESHOLD:2}' failures_threshold: '${FAILURES_THRESHOLD:1}'
# Notify after each REPEATED_FAILURE_NOTIFICATION subsequent failures, 0 to notify only once on first failure # Notify after each REPEATED_FAILURE_NOTIFICATION subsequent failures, 0 to notify only once on first failure
repeated_failure_notification: '${REPEATED_FAILURE_NOTIFICATION:4}' repeated_failure_notification: '${REPEATED_FAILURE_NOTIFICATION:4}'
@ -53,6 +53,8 @@ monitoring:
targets: targets:
# MQTT transport base url, tcp://DOMAIN:1883 by default # MQTT transport base url, tcp://DOMAIN:1883 by default
- base_url: '${MQTT_TRANSPORT_BASE_URL:tcp://${monitoring.domain}:1883}' - base_url: '${MQTT_TRANSPORT_BASE_URL:tcp://${monitoring.domain}:1883}'
# Queue to use for target device
queue: '${MQTT_TRANSPORT_USED_QUEUE:Main}'
# Whether to monitor IPs associated with the domain from base url # Whether to monitor IPs associated with the domain from base url
check_domain_ips: '${MQTT_TRANSPORT_CHECK_DOMAIN_IPS:false}' check_domain_ips: '${MQTT_TRANSPORT_CHECK_DOMAIN_IPS:false}'
# To add more targets, use following environment variables: # To add more targets, use following environment variables:
@ -66,6 +68,8 @@ monitoring:
targets: targets:
# CoAP transport base url, coap://DOMAIN by default # CoAP transport base url, coap://DOMAIN by default
- base_url: '${COAP_TRANSPORT_BASE_URL:coap://${monitoring.domain}}' - base_url: '${COAP_TRANSPORT_BASE_URL:coap://${monitoring.domain}}'
# Queue to use for target device
queue: '${COAP_TRANSPORT_USED_QUEUE:Main}'
# Whether to monitor IPs associated with the domain from base url # Whether to monitor IPs associated with the domain from base url
check_domain_ips: '${COAP_TRANSPORT_CHECK_DOMAIN_IPS:false}' check_domain_ips: '${COAP_TRANSPORT_CHECK_DOMAIN_IPS:false}'
# To add more targets, use following environment variables: # To add more targets, use following environment variables:
@ -79,6 +83,8 @@ monitoring:
targets: targets:
# HTTP transport base url, http://DOMAIN by default # HTTP transport base url, http://DOMAIN by default
- base_url: '${HTTP_TRANSPORT_BASE_URL:http://${monitoring.domain}}' - base_url: '${HTTP_TRANSPORT_BASE_URL:http://${monitoring.domain}}'
# Queue to use for target device
queue: '${HTTP_TRANSPORT_USED_QUEUE:Main}'
# Whether to monitor IPs associated with the domain from base url # Whether to monitor IPs associated with the domain from base url
check_domain_ips: '${HTTP_TRANSPORT_CHECK_DOMAIN_IPS:false}' check_domain_ips: '${HTTP_TRANSPORT_CHECK_DOMAIN_IPS:false}'
# To add more targets, use following environment variables: # To add more targets, use following environment variables:
@ -92,12 +98,15 @@ monitoring:
targets: targets:
# LwM2M transport base url, coap://DOMAIN:5685 by default # LwM2M transport base url, coap://DOMAIN:5685 by default
- base_url: '${LWM2M_TRANSPORT_BASE_URL:coap://${monitoring.domain}:5685}' - base_url: '${LWM2M_TRANSPORT_BASE_URL:coap://${monitoring.domain}:5685}'
# Queue to use for target device
queue: '${LWM2M_TRANSPORT_USED_QUEUE:Main}'
# Whether to monitor IPs associated with the domain from base url # Whether to monitor IPs associated with the domain from base url
check_domain_ips: '${LWM2M_TRANSPORT_CHECK_DOMAIN_IPS:false}' check_domain_ips: '${LWM2M_TRANSPORT_CHECK_DOMAIN_IPS:false}'
# To add more targets, use following environment variables: # To add more targets, use following environment variables:
# monitoring.transports.lwm2m.targets[1].base_url, monitoring.transports.lwm2m.targets[2].base_url, etc. # monitoring.transports.lwm2m.targets[1].base_url, monitoring.transports.lwm2m.targets[2].base_url, etc.
notification_channels: notifications:
message_prefix: '${NOTIFICATION_MESSAGE_PREFIX:}'
slack: slack:
# Enable notifying via Slack # Enable notifying via Slack
enabled: '${SLACK_NOTIFICATION_CHANNEL_ENABLED:false}' enabled: '${SLACK_NOTIFICATION_CHANNEL_ENABLED:false}'

124
rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java

@ -18,6 +18,8 @@ package org.thingsboard.rest.client;
import com.auth0.jwt.JWT; import com.auth0.jwt.JWT;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.base.Strings;
import org.apache.commons.io.IOUtils;
import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
@ -53,11 +55,13 @@ import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.EntityViewInfo; import org.thingsboard.server.common.data.EntityViewInfo;
import org.thingsboard.server.common.data.EventInfo; import org.thingsboard.server.common.data.EventInfo;
import org.thingsboard.server.common.data.ImageExportData;
import org.thingsboard.server.common.data.OtaPackage; import org.thingsboard.server.common.data.OtaPackage;
import org.thingsboard.server.common.data.OtaPackageInfo; import org.thingsboard.server.common.data.OtaPackageInfo;
import org.thingsboard.server.common.data.SaveDeviceWithCredentialsRequest; import org.thingsboard.server.common.data.SaveDeviceWithCredentialsRequest;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.SystemInfo; import org.thingsboard.server.common.data.SystemInfo;
import org.thingsboard.server.common.data.TbImageDeleteResult;
import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.TbResourceInfo; import org.thingsboard.server.common.data.TbResourceInfo;
import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.Tenant;
@ -168,6 +172,7 @@ import org.thingsboard.server.common.data.widget.WidgetTypeInfo;
import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.common.data.widget.WidgetsBundle;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@ -3591,6 +3596,97 @@ public class RestClient implements Closeable {
restTemplate.delete("/api/resource/{resourceId}", resourceId.getId().toString()); restTemplate.delete("/api/resource/{resourceId}", resourceId.getId().toString());
} }
public TbResourceInfo getImageInfo(String type, String key) {
return restTemplate.getForObject(baseURL + "/api/images/{type}/{key}/info", TbResourceInfo.class, Map.of(
"type", type,
"key", key
));
}
public PageData<TbResourceInfo> getImages(PageLink pageLink, boolean includeSystemImages) {
Map<String, String> params = new HashMap<>();
addPageLinkToParam(params, pageLink);
params.put("includeSystemImages", String.valueOf(includeSystemImages));
return restTemplate.exchange(baseURL + "/api/images?includeSystemImages={includeSystemImages}&" + getUrlParams(pageLink),
HttpMethod.GET,
HttpEntity.EMPTY,
new ParameterizedTypeReference<PageData<TbResourceInfo>>() {},
params
).getBody();
}
public TbResourceInfo uploadImage(String fileName, byte[] data, String contentType, String title) {
HttpEntity<MultiValueMap<String, Object>> request = createMultipartRequest(fileName, data, contentType, Map.of(
"title", Strings.nullToEmpty(title)
));
return restTemplate.postForObject(baseURL + "/api/image", request, TbResourceInfo.class);
}
public TbResourceInfo updateImage(String type, String key, String fileName, byte[] data, String contentType) {
HttpEntity<MultiValueMap<String, Object>> request = createMultipartRequest(fileName, data, contentType, Map.of());
return restTemplate.exchange(baseURL + "/api/images/{type}/{key}", HttpMethod.PUT, request, TbResourceInfo.class, Map.of(
"type", type,
"key", key
)).getBody();
}
public TbResourceInfo updateImageInfo(String type, String key, TbResourceInfo request) {
return restTemplate.exchange(baseURL + "/api/images/{type}/{key}/info", HttpMethod.PUT, new HttpEntity<>(request), TbResourceInfo.class, Map.of(
"type", type,
"key", key
)).getBody();
}
public void updateImagePublicStatus(String type, String key, boolean isPublic) {
restTemplate.put(baseURL + "/api/images/{type}/{key}/public/{isPublic}", null, Map.of(
"type", type,
"key", key,
"isPublic", isPublic
));
}
public byte[] downloadImage(String type, String key) throws IOException {
Resource image = restTemplate.exchange(baseURL + "/api/images/{type}/{key}", HttpMethod.GET, null, Resource.class, Map.of(
"type", type,
"key", key
)).getBody();
return IOUtils.toByteArray(image.getInputStream());
}
public byte[] downloadImagePreview(String type, String key) throws IOException {
Resource image = restTemplate.exchange(baseURL + "/api/images/{type}/{key}/preview", HttpMethod.GET, null, Resource.class, Map.of(
"type", type,
"key", key
)).getBody();
return IOUtils.toByteArray(image.getInputStream());
}
public byte[] downloadPublicImage(String publicResourceKey) throws IOException {
Resource image = restTemplate.exchange(baseURL + "/api/images/public/{publicResourceKey}", HttpMethod.GET, null, Resource.class, Map.of(
"publicResourceKey", publicResourceKey
)).getBody();
return IOUtils.toByteArray(image.getInputStream());
}
public ImageExportData exportImage(String type, String key) {
return restTemplate.getForObject(baseURL + "/api/images/{type}/{key}/export", ImageExportData.class, Map.of(
"type", type,
"key", key
));
}
public TbResourceInfo importImage(ImageExportData exportData) {
return restTemplate.exchange(baseURL + "/api/image/import", HttpMethod.PUT, new HttpEntity<>(exportData), TbResourceInfo.class).getBody();
}
public TbImageDeleteResult deleteImage(String type, String key, boolean force) {
return restTemplate.exchange(baseURL + "/api/images/{type}/{key}?force={force}", HttpMethod.DELETE, null, TbImageDeleteResult.class, Map.of(
"type", type,
"key", key,
"force", force
)).getBody();
}
public ResponseEntity<Resource> downloadOtaPackage(OtaPackageId otaPackageId) { public ResponseEntity<Resource> downloadOtaPackage(OtaPackageId otaPackageId) {
Map<String, String> params = new HashMap<>(); Map<String, String> params = new HashMap<>();
params.put("otaPackageId", otaPackageId.getId().toString()); params.put("otaPackageId", otaPackageId.getId().toString());
@ -3640,16 +3736,7 @@ public class RestClient implements Closeable {
} }
public OtaPackageInfo saveOtaPackageData(OtaPackageId otaPackageId, String checkSum, ChecksumAlgorithm checksumAlgorithm, String fileName, byte[] fileBytes) throws Exception { public OtaPackageInfo saveOtaPackageData(OtaPackageId otaPackageId, String checkSum, ChecksumAlgorithm checksumAlgorithm, String fileName, byte[] fileBytes) throws Exception {
HttpHeaders header = new HttpHeaders(); HttpEntity<MultiValueMap<String, Object>> requestEntity = createMultipartRequest(fileName, fileBytes, null, Collections.emptyMap());
header.setContentType(MediaType.MULTIPART_FORM_DATA);
MultiValueMap<String, String> fileMap = new LinkedMultiValueMap<>();
fileMap.add(HttpHeaders.CONTENT_DISPOSITION, "form-data; name=file; filename=" + fileName);
HttpEntity<ByteArrayResource> fileEntity = new HttpEntity<>(new ByteArrayResource(fileBytes), fileMap);
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.add("file", fileEntity);
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, header);
Map<String, String> params = new HashMap<>(); Map<String, String> params = new HashMap<>();
params.put("otaPackageId", otaPackageId.getId().toString()); params.put("otaPackageId", otaPackageId.getId().toString());
@ -3854,6 +3941,23 @@ public class RestClient implements Closeable {
return listToString(list.stream().map(Enum::name).collect(Collectors.toList())); return listToString(list.stream().map(Enum::name).collect(Collectors.toList()));
} }
private HttpEntity<MultiValueMap<String, Object>> createMultipartRequest(String fileName, byte[] fileData, String fileContentType, Map<String, Object> otherParts) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
MultiValueMap<String, String> fileMap = new LinkedMultiValueMap<>();
fileMap.add(HttpHeaders.CONTENT_DISPOSITION, "form-data; name=file; filename=" + fileName);
if (fileContentType != null) {
fileMap.add(HttpHeaders.CONTENT_TYPE, fileContentType);
}
HttpEntity<ByteArrayResource> fileEntity = new HttpEntity<>(new ByteArrayResource(fileData), fileMap);
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.setAll(otherParts);
body.add("file", fileEntity);
return new HttpEntity<>(body, headers);
}
@Override @Override
public void close() { public void close() {
service.shutdown(); service.shutdown();

6
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java

@ -49,6 +49,7 @@ import org.thingsboard.server.dao.alarm.AlarmCommentService;
import org.thingsboard.server.dao.asset.AssetProfileService; import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.audit.AuditLogService;
import org.thingsboard.server.dao.cassandra.CassandraCluster; import org.thingsboard.server.dao.cassandra.CassandraCluster;
import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.dashboard.DashboardService;
@ -59,6 +60,7 @@ import org.thingsboard.server.dao.edge.EdgeEventService;
import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.dao.nosql.CassandraStatementTask; import org.thingsboard.server.dao.nosql.CassandraStatementTask;
import org.thingsboard.server.dao.nosql.TbResultSetFuture; import org.thingsboard.server.dao.nosql.TbResultSetFuture;
import org.thingsboard.server.dao.notification.NotificationRequestService; import org.thingsboard.server.dao.notification.NotificationRequestService;
@ -398,4 +400,8 @@ public interface TbContext {
RuleEngineApiUsageStateService getRuleEngineApiUsageStateService(); RuleEngineApiUsageStateService getRuleEngineApiUsageStateService();
EntityService getEntityService(); EntityService getEntityService();
EventService getEventService();
AuditLogService getAuditLogService();
} }

4
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java

@ -60,7 +60,7 @@ public abstract class TbAbstractAlarmNode<C extends TbAbstractAlarmNodeConfigura
ctx.tellNext(msg, TbNodeConnectionType.FALSE); ctx.tellNext(msg, TbNodeConnectionType.FALSE);
} else if (alarmResult.isCreated) { } else if (alarmResult.isCreated) {
tellNext(ctx, msg, alarmResult, TbMsgType.ENTITY_CREATED, "Created"); tellNext(ctx, msg, alarmResult, TbMsgType.ENTITY_CREATED, "Created");
} else if (alarmResult.isUpdated) { } else if (alarmResult.isUpdated || alarmResult.isSeverityUpdated) {
tellNext(ctx, msg, alarmResult, TbMsgType.ENTITY_UPDATED, "Updated"); tellNext(ctx, msg, alarmResult, TbMsgType.ENTITY_UPDATED, "Updated");
} else if (alarmResult.isCleared) { } else if (alarmResult.isCleared) {
tellNext(ctx, msg, alarmResult, TbMsgType.ALARM_CLEAR, "Cleared"); tellNext(ctx, msg, alarmResult, TbMsgType.ALARM_CLEAR, "Cleared");
@ -93,7 +93,7 @@ public abstract class TbAbstractAlarmNode<C extends TbAbstractAlarmNodeConfigura
TbMsgMetaData metaData = originalMsg.getMetaData().copy(); TbMsgMetaData metaData = originalMsg.getMetaData().copy();
if (alarmResult.isCreated) { if (alarmResult.isCreated) {
metaData.putValue(DataConstants.IS_NEW_ALARM, Boolean.TRUE.toString()); metaData.putValue(DataConstants.IS_NEW_ALARM, Boolean.TRUE.toString());
} else if (alarmResult.isUpdated) { } else if (alarmResult.isUpdated || alarmResult.isSeverityUpdated) {
metaData.putValue(DataConstants.IS_EXISTING_ALARM, Boolean.TRUE.toString()); metaData.putValue(DataConstants.IS_EXISTING_ALARM, Boolean.TRUE.toString());
} else if (alarmResult.isCleared) { } else if (alarmResult.isCleared) {
metaData.putValue(DataConstants.IS_CLEARED_ALARM, Boolean.TRUE.toString()); metaData.putValue(DataConstants.IS_CLEARED_ALARM, Boolean.TRUE.toString());

5
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAlarmResult.java

@ -37,10 +37,11 @@ public class TbAlarmResult {
} }
public static TbAlarmResult fromAlarmResult(AlarmApiCallResult result) { public static TbAlarmResult fromAlarmResult(AlarmApiCallResult result) {
boolean isSeverityChanged = result.isSeverityChanged();
return new TbAlarmResult( return new TbAlarmResult(
result.isCreated(), result.isCreated(),
result.isModified() && !result.isSeverityChanged(), result.isModified() && !isSeverityChanged,
result.isSeverityChanged(), isSeverityChanged,
result.isCleared(), result.isCleared(),
result.getAlarm()); result.getAlarm());
} }

13
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/AbstractTbMsgPushNode.java

@ -27,6 +27,7 @@ import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
@ -94,7 +95,8 @@ public abstract class AbstractTbMsgPushNode<T extends BaseTbMsgPushNodeConfigura
} }
break; break;
case ATTRIBUTES_DELETED: case ATTRIBUTES_DELETED:
List<String> keys = JacksonUtil.convertValue(dataJson.get("attributes"), new TypeReference<>() {}); List<String> keys = JacksonUtil.convertValue(dataJson.get("attributes"), new TypeReference<>() {
});
entityBody.put("keys", keys); entityBody.put("keys", keys);
entityBody.put(SCOPE, getScope(metadata)); entityBody.put(SCOPE, getScope(metadata));
break; break;
@ -138,9 +140,8 @@ public abstract class AbstractTbMsgPushNode<T extends BaseTbMsgPushNodeConfigura
abstract void processMsg(TbContext ctx, TbMsg msg); abstract void processMsg(TbContext ctx, TbMsg msg);
protected UUID getUUIDFromMsgData(TbMsg msg) { protected UUID getUUIDFromMsgData(TbMsg msg) {
JsonNode data = JacksonUtil.toJsonNode(msg.getData()).get("id"); Alarm alarm = JacksonUtil.fromString(msg.getData(), Alarm.class);
String id = JacksonUtil.convertValue(data.get("id"), String.class); return alarm != null ? alarm.getUuidId() : null;
return UUID.fromString(id);
} }
protected String getScope(Map<String, String> metadata) { protected String getScope(Map<String, String> metadata) {
@ -174,7 +175,7 @@ public abstract class AbstractTbMsgPushNode<T extends BaseTbMsgPushNodeConfigura
} }
protected boolean isSupportedMsgType(TbMsg msg) { protected boolean isSupportedMsgType(TbMsg msg) {
return msg.isTypeOneOf(POST_TELEMETRY_REQUEST, POST_ATTRIBUTES_REQUEST, ATTRIBUTES_UPDATED, return msg.isTypeOneOf(POST_TELEMETRY_REQUEST, POST_ATTRIBUTES_REQUEST, ATTRIBUTES_UPDATED, ATTRIBUTES_DELETED, TIMESERIES_UPDATED,
ATTRIBUTES_DELETED, TIMESERIES_UPDATED, ALARM, CONNECT_EVENT, DISCONNECT_EVENT, ACTIVITY_EVENT, INACTIVITY_EVENT); ALARM, CONNECT_EVENT, DISCONNECT_EVENT, ACTIVITY_EVENT, INACTIVITY_EVENT);
} }
} }

10
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToCloudNode.java

@ -38,15 +38,7 @@ import java.util.UUID;
"This node used only on edge to push messages from edge to cloud. " + "This node used only on edge to push messages from edge to cloud. " +
"Once message arrived into this node it’s going to be converted into cloud event and saved to the local database. " + "Once message arrived into this node it’s going to be converted into cloud event and saved to the local database. " +
"Node doesn't push messages directly to cloud, but stores event(s) in the cloud queue. " + "Node doesn't push messages directly to cloud, but stores event(s) in the cloud queue. " +
"<br>Supports next originator types:" + "Supports next message types:" +
"<br><code>DEVICE</code>" +
"<br><code>ASSET</code>" +
"<br><code>ENTITY_VIEW</code>" +
"<br><code>DASHBOARD</code>" +
"<br><code>TENANT</code>" +
"<br><code>CUSTOMER</code>" +
"<br><code>EDGE</code><br><br>" +
"As well node supports next message types:" +
"<br><code>POST_TELEMETRY_REQUEST</code>" + "<br><code>POST_TELEMETRY_REQUEST</code>" +
"<br><code>POST_ATTRIBUTES_REQUEST</code>" + "<br><code>POST_ATTRIBUTES_REQUEST</code>" +
"<br><code>ATTRIBUTES_UPDATED</code>" + "<br><code>ATTRIBUTES_UPDATED</code>" +

36
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNode.java

@ -31,8 +31,7 @@ import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageDataIterableByTenantIdEntityId;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
@ -52,21 +51,13 @@ import java.util.UUID;
"This node used only on cloud instances to push messages from cloud to edge. " + "This node used only on cloud instances to push messages from cloud to edge. " +
"Once message arrived into this node it’s going to be converted into edge event and saved to the database. " + "Once message arrived into this node it’s going to be converted into edge event and saved to the database. " +
"Node doesn't push messages directly to edge, but stores event(s) in the edge queue. " + "Node doesn't push messages directly to edge, but stores event(s) in the edge queue. " +
"<br>Supports next originator types:" + "Supports next message types:" +
"<br><code>DEVICE</code>" +
"<br><code>ASSET</code>" +
"<br><code>ENTITY_VIEW</code>" +
"<br><code>DASHBOARD</code>" +
"<br><code>TENANT</code>" +
"<br><code>CUSTOMER</code>" +
"<br><code>EDGE</code><br><br>" +
"As well node supports next message types:" +
"<br><code>POST_TELEMETRY_REQUEST</code>" + "<br><code>POST_TELEMETRY_REQUEST</code>" +
"<br><code>POST_ATTRIBUTES_REQUEST</code>" + "<br><code>POST_ATTRIBUTES_REQUEST</code>" +
"<br><code>ATTRIBUTES_UPDATED</code>" + "<br><code>ATTRIBUTES_UPDATED</code>" +
"<br><code>ATTRIBUTES_DELETED</code>" + "<br><code>ATTRIBUTES_DELETED</code>" +
"<br><code>ALARM</code><br><br>" + "<br><code>ALARM</code><br><br>" +
"Message will be routed via <b>Failure</b> route if node was not able to save edge event to database or unsupported originator type/message type arrived. " + "Message will be routed via <b>Failure</b> route if node was not able to save edge event to database or unsupported message type arrived. " +
"In case successful storage edge event to database message will be routed via <b>Success</b> route.", "In case successful storage edge event to database message will be routed via <b>Success</b> route.",
uiResources = {"static/rulenode/rulenode-core-config.js"}, uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbActionNodePushToEdgeConfig", configDirective = "tbActionNodePushToEdgeConfig",
@ -129,21 +120,13 @@ public class TbMsgPushToEdgeNode extends AbstractTbMsgPushNode<TbMsgPushToEdgeNo
}; };
Futures.addCallback(future, futureCallback, ctx.getDbCallbackExecutor()); Futures.addCallback(future, futureCallback, ctx.getDbCallbackExecutor());
} else { } else {
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
PageData<EdgeId> pageData;
List<ListenableFuture<Void>> futures = new ArrayList<>(); List<ListenableFuture<Void>> futures = new ArrayList<>();
do { PageDataIterableByTenantIdEntityId<EdgeId> edgeIds = new PageDataIterableByTenantIdEntityId<>(
pageData = ctx.getEdgeService().findRelatedEdgeIdsByEntityId(ctx.getTenantId(), msg.getOriginator(), pageLink); ctx.getEdgeService()::findRelatedEdgeIdsByEntityId, ctx.getTenantId(), msg.getOriginator(), DEFAULT_PAGE_SIZE);
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { for (EdgeId edgeId : edgeIds) {
for (EdgeId edgeId : pageData.getData()) { EdgeEvent edgeEvent = buildEvent(msg, ctx);
EdgeEvent edgeEvent = buildEvent(msg, ctx); futures.add(notifyEdge(ctx, edgeEvent, edgeId));
futures.add(notifyEdge(ctx, edgeEvent, edgeId)); }
}
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
}
} while (pageData != null && pageData.hasNext());
if (futures.isEmpty()) { if (futures.isEmpty()) {
// ack in case no edges are related to provided entity // ack in case no edges are related to provided entity
@ -176,5 +159,4 @@ public class TbMsgPushToEdgeNode extends AbstractTbMsgPushNode<TbMsgPushToEdgeNo
return null; return null;
}, ctx.getDbCallbackExecutor()); }, ctx.getDbCallbackExecutor());
} }
} }

1
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java

@ -253,6 +253,7 @@ public class TbAlarmNodeTest {
AlarmApiCallResult.builder() AlarmApiCallResult.builder()
.successful(true) .successful(true)
.modified(true) .modified(true)
.old(new Alarm(activeAlarm))
.alarm(new AlarmInfo(expectedAlarm)) .alarm(new AlarmInfo(expectedAlarm))
.build()); .build());
node.onMsg(ctx, msg); node.onMsg(ctx, msg);

11
transport/coap/src/main/resources/tb-coap-transport.yml

@ -175,8 +175,15 @@ transport:
# Interval of periodic eviction of the timed-out DTLS sessions # Interval of periodic eviction of the timed-out DTLS sessions
dtls_session_report_timeout: "${TB_COAP_X509_DTLS_SESSION_REPORT_TIMEOUT:1800000}" dtls_session_report_timeout: "${TB_COAP_X509_DTLS_SESSION_REPORT_TIMEOUT:1800000}"
sessions: sessions:
# Inactivity timeout for device session in transport service. The last activity time of the device session is updated if the device sends any message, including keepalive messages # Session inactivity timeout is a global configuration parameter that defines how long the device transport session will be opened after the last message arrives from the device.
inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}" # The parameter value is in milliseconds.
# The last activity time of the device session is updated if the device sends any message, including keepalive messages
# If there is no activity, the session will be closed, and all subscriptions will be deleted.
# We recommend this parameter to be in sync with device inactivity timeout ("state.defaultInactivityTimeoutInSec" or DEFAULT_INACTIVITY_TIMEOUT) parameter
# which is responsible for detection of the device connectivity status in the core service of the platform.
# The value of the session inactivity timeout parameter should be greater or equal to the device inactivity timeout.
# Note that the session inactivity timeout is set in milliseconds while device inactivity timeout is in seconds.
inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:600000}"
# Interval of periodic check for expired sessions and report of the changes to session last activity time # Interval of periodic check for expired sessions and report of the changes to session last activity time
report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:3000}" report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:3000}"
json: json:

11
transport/http/src/main/resources/tb-http-transport.yml

@ -160,8 +160,15 @@ transport:
# HTTP maximum request processing timeout in milliseconds # HTTP maximum request processing timeout in milliseconds
max_request_timeout: "${HTTP_MAX_REQUEST_TIMEOUT:300000}" max_request_timeout: "${HTTP_MAX_REQUEST_TIMEOUT:300000}"
sessions: sessions:
# Inactivity timeout for device session in transport service. The last activity time of the device session is updated if device sends any message, including keepalive messages # Session inactivity timeout is a global configuration parameter that defines how long the device transport session will be opened after the last message arrives from the device.
inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}" # The parameter value is in milliseconds.
# The last activity time of the device session is updated if the device sends any message, including keepalive messages
# If there is no activity, the session will be closed, and all subscriptions will be deleted.
# We recommend this parameter to be in sync with device inactivity timeout ("state.defaultInactivityTimeoutInSec" or DEFAULT_INACTIVITY_TIMEOUT) parameter
# which is responsible for detection of the device connectivity status in the core service of the platform.
# The value of the session inactivity timeout parameter should be greater or equal to the device inactivity timeout.
# Note that the session inactivity timeout is set in milliseconds while device inactivity timeout is in seconds.
inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:600000}"
# Interval of periodic check for expired sessions and report of the changes to session last activity time # Interval of periodic check for expired sessions and report of the changes to session last activity time
report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:3000}" report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:3000}"
json: json:

11
transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml

@ -122,8 +122,15 @@ redis:
# LWM2M server parameters # LWM2M server parameters
transport: transport:
sessions: sessions:
# Inactivity timeout for device session in transport service. The last activity time of the device session is updated if the device sends any message, including keepalive messages # Session inactivity timeout is a global configuration parameter that defines how long the device transport session will be opened after the last message arrives from the device.
inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}" # The parameter value is in milliseconds.
# The last activity time of the device session is updated if the device sends any message, including keepalive messages
# If there is no activity, the session will be closed, and all subscriptions will be deleted.
# We recommend this parameter to be in sync with device inactivity timeout ("state.defaultInactivityTimeoutInSec" or DEFAULT_INACTIVITY_TIMEOUT) parameter
# which is responsible for detection of the device connectivity status in the core service of the platform.
# The value of the session inactivity timeout parameter should be greater or equal to the device inactivity timeout.
# Note that the session inactivity timeout is set in milliseconds while device inactivity timeout is in seconds.
inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:600000}"
# Interval of periodic check for expired sessions and report of the changes to session last activity time # Interval of periodic check for expired sessions and report of the changes to session last activity time
report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:3000}" report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:3000}"
json: json:

11
transport/mqtt/src/main/resources/tb-mqtt-transport.yml

@ -181,8 +181,15 @@ transport:
# Skip certificate validity check for client certificates. # Skip certificate validity check for client certificates.
skip_validity_check_for_client_cert: "${MQTT_SSL_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}" skip_validity_check_for_client_cert: "${MQTT_SSL_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}"
sessions: sessions:
# Inactivity timeout for device session in transport service. The last activity time of the device session is updated if the device sends any message, including keepalive messages # Session inactivity timeout is a global configuration parameter that defines how long the device transport session will be opened after the last message arrives from the device.
inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}" # The parameter value is in milliseconds.
# The last activity time of the device session is updated if the device sends any message, including keepalive messages
# If there is no activity, the session will be closed, and all subscriptions will be deleted.
# We recommend this parameter to be in sync with device inactivity timeout ("state.defaultInactivityTimeoutInSec" or DEFAULT_INACTIVITY_TIMEOUT) parameter
# which is responsible for detection of the device connectivity status in the core service of the platform.
# The value of the session inactivity timeout parameter should be greater or equal to the device inactivity timeout.
# Note that the session inactivity timeout is set in milliseconds while device inactivity timeout is in seconds.
inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:600000}"
# Interval of periodic check for expired sessions and report of the changes to session last activity time # Interval of periodic check for expired sessions and report of the changes to session last activity time
report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:3000}" report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:3000}"
json: json:

11
transport/snmp/src/main/resources/tb-snmp-transport.yml

@ -137,8 +137,15 @@ transport:
# To ignore SNMP response values that do not match the data type of the configured OID mapping (by default false - will throw an error if any value of the response not match configured data types) # To ignore SNMP response values that do not match the data type of the configured OID mapping (by default false - will throw an error if any value of the response not match configured data types)
ignore_type_cast_errors: "${SNMP_RESPONSE_IGNORE_TYPE_CAST_ERRORS:false}" ignore_type_cast_errors: "${SNMP_RESPONSE_IGNORE_TYPE_CAST_ERRORS:false}"
sessions: sessions:
# Inactivity timeout for device session in transport service. The last activity time of the device session is updated if the device sends any message, including keepalive messages # Session inactivity timeout is a global configuration parameter that defines how long the device transport session will be opened after the last message arrives from the device.
inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}" # The parameter value is in milliseconds.
# The last activity time of the device session is updated if the device sends any message, including keepalive messages
# If there is no activity, the session will be closed, and all subscriptions will be deleted.
# We recommend this parameter to be in sync with device inactivity timeout ("state.defaultInactivityTimeoutInSec" or DEFAULT_INACTIVITY_TIMEOUT) parameter
# which is responsible for detection of the device connectivity status in the core service of the platform.
# The value of the session inactivity timeout parameter should be greater or equal to the device inactivity timeout.
# Note that the session inactivity timeout is set in milliseconds while device inactivity timeout is in seconds.
inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:600000}"
# Interval of periodic check for expired sessions and report of the changes to session last activity time # Interval of periodic check for expired sessions and report of the changes to session last activity time
report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:3000}" report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:3000}"
json: json:

2
ui-ngx/src/app/modules/home/components/widget/config/widget-settings.component.ts

@ -246,7 +246,7 @@ export class WidgetSettingsComponent implements ControlValueAccessor, OnInit, On
}; };
} }
} else if (this.useJsonForm()) { } else if (this.useJsonForm()) {
if (!this.widgetSettingsFormGroup.valid) { if (!this.widgetSettingsFormGroup.get('settings').valid) {
return { return {
widgetSettings: { widgetSettings: {
valid: false valid: false

Loading…
Cancel
Save