Browse Source

Merge remote-tracking branch 'upstream/rc' into AD/bug-fix/scada-bad-xml

pull/13110/head
Artem Dzhereleiko 1 year ago
parent
commit
9bcdc86d09
  1. 10
      application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java
  2. 16
      application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerMessageProcessor.java
  3. 5
      application/src/main/java/org/thingsboard/server/controller/AuthController.java
  4. 23
      application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java
  5. 12
      application/src/main/java/org/thingsboard/server/controller/EventController.java
  6. 3
      application/src/main/java/org/thingsboard/server/service/cf/DefaultCalculatedFieldCache.java
  7. 3
      application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java
  8. 88
      application/src/main/java/org/thingsboard/server/service/edge/EdgeMsgConstructorUtils.java
  9. 12
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java
  10. 6
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java
  11. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EdgeProcessor.java
  12. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessor.java
  13. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/comment/AlarmCommentEdgeProcessor.java
  14. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessor.java
  15. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/AssetProfileEdgeProcessor.java
  16. 28
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/customer/CustomerEdgeProcessor.java
  17. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardEdgeProcessor.java
  18. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java
  19. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/DeviceProfileEdgeProcessor.java
  20. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/edge/EdgeEntityProcessor.java
  21. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewEdgeProcessor.java
  22. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/notification/NotificationRuleEdgeProcessor.java
  23. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/notification/NotificationTargetEdgeProcessor.java
  24. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/notification/NotificationTemplateEdgeProcessor.java
  25. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/oauth2/DomainEdgeProcessor.java
  26. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/oauth2/OAuth2ClientEdgeProcessor.java
  27. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/ota/OtaPackageEdgeProcessor.java
  28. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/queue/QueueEdgeProcessor.java
  29. 4
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessor.java
  30. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessor.java
  31. 5
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainEdgeProcessor.java
  32. 5
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainMetadataEdgeProcessor.java
  33. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/settings/AdminSettingsEdgeProcessor.java
  34. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantEdgeProcessor.java
  35. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantProfileEdgeProcessor.java
  36. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/user/UserEdgeProcessor.java
  37. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetBundleEdgeProcessor.java
  38. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetTypeEdgeProcessor.java
  39. 1
      application/src/main/java/org/thingsboard/server/service/edge/rpc/utils/EdgeVersionUtils.java
  40. 44
      application/src/main/java/org/thingsboard/server/service/edqs/EdqsSyncService.java
  41. 2
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCalculatedFieldConsumerService.java
  42. 14
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java
  43. 2
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
  44. 26
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbEdgeConsumerService.java
  45. 2
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java
  46. 6
      application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java
  47. 19
      application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractPartitionBasedConsumerService.java
  48. 26
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java
  49. 2
      application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java
  50. 62
      application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java
  51. 16
      application/src/main/resources/banner.txt
  52. 34
      application/src/main/resources/thingsboard.yml
  53. 9
      application/src/test/java/org/thingsboard/server/controller/CalculatedFieldControllerTest.java
  54. 8
      application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java
  55. 4
      application/src/test/java/org/thingsboard/server/controller/EdgeControllerTest.java
  56. 11
      application/src/test/java/org/thingsboard/server/controller/EntityQueryControllerTest.java
  57. 14
      application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java
  58. 11
      application/src/test/java/org/thingsboard/server/controller/UserControllerTest.java
  59. 29
      application/src/test/java/org/thingsboard/server/controller/WebsocketApiTest.java
  60. 4
      application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java
  61. 14
      application/src/test/java/org/thingsboard/server/edge/TenantProfileEdgeTest.java
  62. 156
      application/src/test/java/org/thingsboard/server/service/edge/EdgeMsgConstructorUtilsTest.java
  63. 21
      application/src/test/java/org/thingsboard/server/service/entitiy/EntityServiceTest.java
  64. 2
      application/src/test/java/org/thingsboard/server/service/sync/vc/VersionControlTest.java
  65. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/customer/CustomerService.java
  66. 2
      common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java
  67. 4
      common/data/src/main/java/org/thingsboard/server/common/data/JavaSerDesUtil.java
  68. 26
      common/data/src/main/java/org/thingsboard/server/common/data/cf/CalculatedField.java
  69. 3
      common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/ScriptCalculatedFieldConfiguration.java
  70. 3
      common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/SimpleCalculatedFieldConfiguration.java
  71. 2
      common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/FieldsUtil.java
  72. 5
      common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/UserFields.java
  73. 3
      common/data/src/main/java/org/thingsboard/server/common/data/event/CalculatedFieldDebugEventFilter.java
  74. 2
      common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityExportData.java
  75. 6
      common/edqs/src/main/java/org/thingsboard/server/edqs/data/dp/BoolDataPoint.java
  76. 3
      common/edqs/src/main/java/org/thingsboard/server/edqs/data/dp/CompressedStringDataPoint.java
  77. 6
      common/edqs/src/main/java/org/thingsboard/server/edqs/data/dp/DoubleDataPoint.java
  78. 2
      common/edqs/src/main/java/org/thingsboard/server/edqs/data/dp/JsonDataPoint.java
  79. 8
      common/edqs/src/main/java/org/thingsboard/server/edqs/data/dp/LongDataPoint.java
  80. 2
      common/edqs/src/main/java/org/thingsboard/server/edqs/data/dp/StringDataPoint.java
  81. 5
      common/edqs/src/main/java/org/thingsboard/server/edqs/repo/DefaultEdqsRepository.java
  82. 34
      common/edqs/src/main/java/org/thingsboard/server/edqs/repo/TenantRepo.java
  83. 92
      common/edqs/src/main/java/org/thingsboard/server/edqs/stats/DefaultEdqsStatsService.java
  84. 81
      common/edqs/src/main/java/org/thingsboard/server/edqs/stats/EdqsStatsService.java
  85. 45
      common/edqs/src/main/java/org/thingsboard/server/edqs/util/EdqsConverter.java
  86. 2
      common/edqs/src/main/java/org/thingsboard/server/edqs/util/RepositoryUtils.java
  87. 4
      common/queue/src/main/java/org/thingsboard/server/queue/discovery/TopicService.java
  88. 24
      common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaSettings.java
  89. 1
      common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueProducerProvider.java
  90. 41
      common/stats/src/main/java/org/thingsboard/server/common/stats/DummyEdqsStatsService.java
  91. 33
      common/stats/src/main/java/org/thingsboard/server/common/stats/EdqsStatsService.java
  92. 12
      common/stats/src/main/java/org/thingsboard/server/common/stats/StatsTimer.java
  93. 5
      common/util/pom.xml
  94. 78
      common/util/src/main/java/org/thingsboard/common/util/ExpressionFunctionsUtil.java
  95. 2
      common/util/src/main/java/org/thingsboard/common/util/TbBytePool.java
  96. 2
      common/util/src/main/java/org/thingsboard/common/util/TbStringPool.java
  97. 34
      common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/GitRepository.java
  98. 19
      dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java
  99. 6
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityServiceImpl.java
  100. 48
      dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java

10
application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java

@ -107,7 +107,7 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM
public void process(CalculatedFieldStateRestoreMsg msg) { public void process(CalculatedFieldStateRestoreMsg msg) {
CalculatedFieldId cfId = msg.getId().cfId(); CalculatedFieldId cfId = msg.getId().cfId();
log.info("[{}] [{}] Processing CF state restore msg.", msg.getId().entityId(), cfId); log.debug("[{}] [{}] Processing CF state restore msg.", msg.getId().entityId(), cfId);
if (msg.getState() != null) { if (msg.getState() != null) {
states.put(cfId, msg.getState()); states.put(cfId, msg.getState());
} else { } else {
@ -116,10 +116,10 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM
} }
public void process(EntityInitCalculatedFieldMsg msg) throws CalculatedFieldException { public void process(EntityInitCalculatedFieldMsg msg) throws CalculatedFieldException {
log.info("[{}] Processing entity init CF msg.", msg.getCtx().getCfId()); log.debug("[{}] Processing entity init CF msg.", msg.getCtx().getCfId());
var ctx = msg.getCtx(); var ctx = msg.getCtx();
if (msg.isForceReinit()) { if (msg.isForceReinit()) {
log.info("Force reinitialization of CF: [{}].", ctx.getCfId()); log.debug("Force reinitialization of CF: [{}].", ctx.getCfId());
states.remove(ctx.getCfId()); states.remove(ctx.getCfId());
} }
try { try {
@ -138,7 +138,7 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM
} }
public void process(CalculatedFieldEntityDeleteMsg msg) { public void process(CalculatedFieldEntityDeleteMsg msg) {
log.info("[{}] Processing CF entity delete msg.", msg.getEntityId()); log.debug("[{}] Processing CF entity delete msg.", msg.getEntityId());
if (this.entityId.equals(msg.getEntityId())) { if (this.entityId.equals(msg.getEntityId())) {
if (states.isEmpty()) { if (states.isEmpty()) {
msg.getCallback().onSuccess(); msg.getCallback().onSuccess();
@ -244,7 +244,7 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM
private void processArgumentValuesUpdate(CalculatedFieldCtx ctx, List<CalculatedFieldId> cfIdList, MultipleTbCallback callback, private void processArgumentValuesUpdate(CalculatedFieldCtx ctx, List<CalculatedFieldId> cfIdList, MultipleTbCallback callback,
Map<String, ArgumentEntry> newArgValues, UUID tbMsgId, TbMsgType tbMsgType) throws CalculatedFieldException { Map<String, ArgumentEntry> newArgValues, UUID tbMsgId, TbMsgType tbMsgType) throws CalculatedFieldException {
if (newArgValues.isEmpty()) { if (newArgValues.isEmpty()) {
log.info("[{}] No new argument values to process for CF.", ctx.getCfId()); log.debug("[{}] No new argument values to process for CF.", ctx.getCfId());
callback.onSuccess(CALLBACKS_PER_CF); callback.onSuccess(CALLBACKS_PER_CF);
} }
CalculatedFieldState state = states.get(ctx.getCfId()); CalculatedFieldState state = states.get(ctx.getCfId());

16
application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerMessageProcessor.java

@ -236,12 +236,12 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
private void onCfCreated(ComponentLifecycleMsg msg, TbCallback callback) throws CalculatedFieldException { private void onCfCreated(ComponentLifecycleMsg msg, TbCallback callback) throws CalculatedFieldException {
var cfId = new CalculatedFieldId(msg.getEntityId().getId()); var cfId = new CalculatedFieldId(msg.getEntityId().getId());
if (calculatedFields.containsKey(cfId)) { if (calculatedFields.containsKey(cfId)) {
log.warn("[{}] CF was already initialized [{}]", tenantId, cfId); log.debug("[{}] CF was already initialized [{}]", tenantId, cfId);
callback.onSuccess(); callback.onSuccess();
} else { } else {
var cf = cfDaoService.findById(msg.getTenantId(), cfId); var cf = cfDaoService.findById(msg.getTenantId(), cfId);
if (cf == null) { if (cf == null) {
log.warn("[{}] Failed to lookup CF by id [{}]", tenantId, cfId); log.debug("[{}] Failed to lookup CF by id [{}]", tenantId, cfId);
callback.onSuccess(); callback.onSuccess();
} else { } else {
var cfCtx = new CalculatedFieldCtx(cf, systemContext.getTbelInvokeService(), systemContext.getApiLimitService()); var cfCtx = new CalculatedFieldCtx(cf, systemContext.getTbelInvokeService(), systemContext.getApiLimitService());
@ -268,7 +268,7 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
} else { } else {
var newCf = cfDaoService.findById(msg.getTenantId(), cfId); var newCf = cfDaoService.findById(msg.getTenantId(), cfId);
if (newCf == null) { if (newCf == null) {
log.warn("[{}] Failed to lookup CF by id [{}]", tenantId, cfId); log.debug("[{}] Failed to lookup CF by id [{}]", tenantId, cfId);
callback.onSuccess(); callback.onSuccess();
} else { } else {
var newCfCtx = new CalculatedFieldCtx(newCf, systemContext.getTbelInvokeService(), systemContext.getApiLimitService()); var newCfCtx = new CalculatedFieldCtx(newCf, systemContext.getTbelInvokeService(), systemContext.getApiLimitService());
@ -313,7 +313,7 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
var cfId = new CalculatedFieldId(msg.getEntityId().getId()); var cfId = new CalculatedFieldId(msg.getEntityId().getId());
var cfCtx = calculatedFields.remove(cfId); var cfCtx = calculatedFields.remove(cfId);
if (cfCtx == null) { if (cfCtx == null) {
log.warn("[{}] CF was already deleted [{}]", tenantId, cfId); log.debug("[{}] CF was already deleted [{}]", tenantId, cfId);
callback.onSuccess(); callback.onSuccess();
} else { } else {
entityIdCalculatedFields.get(cfCtx.getEntityId()).remove(cfCtx); entityIdCalculatedFields.get(cfCtx.getEntityId()).remove(cfCtx);
@ -390,9 +390,11 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
msg.getCallback().onSuccess(); msg.getCallback().onSuccess();
} }
} else { } else {
log.debug("Pushing linked telemetry msg to specific actor [{}]", targetEntityId); if (isMyPartition(targetEntityId, msg.getCallback())) {
var newMsg = new EntityCalculatedFieldLinkedTelemetryMsg(tenantId, sourceEntityId, proto.getMsg(), cf, msg.getCallback()); log.debug("Pushing linked telemetry msg to specific actor [{}]", targetEntityId);
getOrCreateActor(targetEntityId).tell(newMsg); var newMsg = new EntityCalculatedFieldLinkedTelemetryMsg(tenantId, sourceEntityId, proto.getMsg(), cf, msg.getCallback());
getOrCreateActor(targetEntityId).tell(newMsg);
}
} }
} }
} }

5
application/src/main/java/org/thingsboard/server/controller/AuthController.java

@ -228,10 +228,9 @@ public class AuthController extends BaseController {
@ApiOperation(value = "Reset password (resetPassword)", @ApiOperation(value = "Reset password (resetPassword)",
notes = "Checks the password reset token and updates the password. " + notes = "Checks the password reset token and updates the password. " +
"If token is valid, returns the object that contains [JWT](https://jwt.io/) access and refresh tokens. " +
"If token is not valid, returns '400 Bad Request'.") "If token is not valid, returns '400 Bad Request'.")
@PostMapping(value = "/noauth/resetPassword") @PostMapping(value = "/noauth/resetPassword")
public JwtPair resetPassword(@Parameter(description = "Reset password request.") public void resetPassword(@Parameter(description = "Reset password request.")
@RequestBody ResetPasswordRequest resetPasswordRequest, @RequestBody ResetPasswordRequest resetPasswordRequest,
HttpServletRequest request) throws ThingsboardException { HttpServletRequest request) throws ThingsboardException {
String resetToken = resetPasswordRequest.getResetToken(); String resetToken = resetPasswordRequest.getResetToken();
@ -263,8 +262,6 @@ public class AuthController extends BaseController {
} }
eventPublisher.publishEvent(new UserCredentialsInvalidationEvent(securityUser.getId())); eventPublisher.publishEvent(new UserCredentialsInvalidationEvent(securityUser.getId()));
return tokenFactory.createTokenPair(securityUser);
} else { } else {
throw new ThingsboardException("Invalid reset token!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); throw new ThingsboardException("Invalid reset token!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
} }

23
application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java

@ -189,6 +189,29 @@ public class ControllerConstants {
protected static final String EVENT_DEBUG_RULE_CHAIN_FILTER_OBJ = MARKDOWN_CODE_BLOCK_START + "{\n" + protected static final String EVENT_DEBUG_RULE_CHAIN_FILTER_OBJ = MARKDOWN_CODE_BLOCK_START + "{\n" +
" \"eventType\":\"DEBUG_RULE_CHAIN\",\n" + DEBUG_FILTER_OBJ + MARKDOWN_CODE_BLOCK_END; " \"eventType\":\"DEBUG_RULE_CHAIN\",\n" + DEBUG_FILTER_OBJ + MARKDOWN_CODE_BLOCK_END;
protected static final String EVENT_DEBUG_CALCULATED_FIELD_FILTER_OBJ = MARKDOWN_CODE_BLOCK_START + "{\n" +
" \"eventType\":\"DEBUG_CALCULATED_FIELD\",\n" +
" \"server\":\"ip-172-31-24-152\",\n" +
" \"isError\":\"false\",\n" +
" \"errorStr\":\"Error Message\"\n" +
" \"entityId\":\"cf4b8741-f618-471f-ae08-d881ca7f9fe9\",\n" +
" \"msgId\":\"5cf7d3a0-aee7-40dd-a737-ade05528e7eb\",\n" +
" \"msgType\":\"POST_TELEMETRY_REQUEST\",\n" +
" \"arguments\":\"{\n" +
" \"x\": {\n" +
" \"ts\": 1739432016629,\n" +
" \"value\": 20\n" +
" },\n" +
" \"y\": {\n" +
" \"ts\": 1739429717656,\n" +
" \"value\": 12\n" +
" }\n" +
" }\",\n" +
" \"result\":\"{\n" +
" \"x + y\": 32\n" +
" }\",\n" +
"}" + MARKDOWN_CODE_BLOCK_END;
protected static final String IS_BOOTSTRAP_SERVER_PARAM_DESCRIPTION = "A Boolean value representing the Server SecurityInfo for future Bootstrap client mode settings. Values: 'true' for Bootstrap Server; 'false' for Lwm2m Server. "; protected static final String IS_BOOTSTRAP_SERVER_PARAM_DESCRIPTION = "A Boolean value representing the Server SecurityInfo for future Bootstrap client mode settings. Values: 'true' for Bootstrap Server; 'false' for Lwm2m Server. ";
protected static final String DEVICE_WITH_DEVICE_CREDENTIALS_ACCESS_TOKEN_PARAM_DESCRIPTION = protected static final String DEVICE_WITH_DEVICE_CREDENTIALS_ACCESS_TOKEN_PARAM_DESCRIPTION =

12
application/src/main/java/org/thingsboard/server/controller/EventController.java

@ -47,6 +47,7 @@ import static org.thingsboard.server.controller.ControllerConstants.ENTITY_ID;
import static org.thingsboard.server.controller.ControllerConstants.ENTITY_ID_PARAM_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.ENTITY_ID_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.ENTITY_TYPE; import static org.thingsboard.server.controller.ControllerConstants.ENTITY_TYPE;
import static org.thingsboard.server.controller.ControllerConstants.ENTITY_TYPE_PARAM_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.ENTITY_TYPE_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.EVENT_DEBUG_CALCULATED_FIELD_FILTER_OBJ;
import static org.thingsboard.server.controller.ControllerConstants.EVENT_DEBUG_RULE_CHAIN_FILTER_OBJ; import static org.thingsboard.server.controller.ControllerConstants.EVENT_DEBUG_RULE_CHAIN_FILTER_OBJ;
import static org.thingsboard.server.controller.ControllerConstants.EVENT_DEBUG_RULE_NODE_FILTER_OBJ; import static org.thingsboard.server.controller.ControllerConstants.EVENT_DEBUG_RULE_NODE_FILTER_OBJ;
import static org.thingsboard.server.controller.ControllerConstants.EVENT_END_TIME_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.EVENT_END_TIME_DESCRIPTION;
@ -70,7 +71,7 @@ import static org.thingsboard.server.controller.ControllerConstants.TENANT_ID_PA
public class EventController extends BaseController { public class EventController extends BaseController {
private static final String EVENT_FILTER_DEFINITION = "# Event Filter Definition" + NEW_LINE + private static final String EVENT_FILTER_DEFINITION = "# Event Filter Definition" + NEW_LINE +
"5 different eventFilter objects could be set for different event types. " + "6 different eventFilter objects could be set for different event types. " +
"The eventType field is required. Others are optional. If some of them are set, the filtering will be applied according to them. " + "The eventType field is required. Others are optional. If some of them are set, the filtering will be applied according to them. " +
"See the examples below for all the fields used for each event type filtering. " + NEW_LINE + "See the examples below for all the fields used for each event type filtering. " + NEW_LINE +
"Note," + NEW_LINE + "Note," + NEW_LINE +
@ -98,6 +99,15 @@ public class EventController extends BaseController {
" * 'relationType' - string value representing the type of message routing;\n" + " * 'relationType' - string value representing the type of message routing;\n" +
" * 'entityId' - string value representing the entity id in the event body (originator of the message);\n" + " * 'entityId' - string value representing the entity id in the event body (originator of the message);\n" +
" * 'msgType' - string value representing the message type;\n" + " * 'msgType' - string value representing the message type;\n" +
" * 'isError' - boolean value to filter the errors." + NEW_LINE +
"## Debug Calculated Field Event Filter" + NEW_LINE +
EVENT_DEBUG_CALCULATED_FIELD_FILTER_OBJ + NEW_LINE +
" * 'entityId' - string value representing the entity id in the event body;\n" +
" * 'entityType' - string value representing the entity type;\n" +
" * 'msgId' - string value representing the message id in the rule engine;\n" +
" * 'msgType' - string value representing the message type;\n" +
" * 'arguments' - string value representing the arguments that were used in the calculation performed;\n" +
" * 'result' - string value representing the result of a calculation;\n" +
" * 'isError' - boolean value to filter the errors." + NEW_LINE; " * 'isError' - boolean value to filter the errors." + NEW_LINE;
@Autowired @Autowired

3
application/src/main/java/org/thingsboard/server/service/cf/DefaultCalculatedFieldCache.java

@ -144,6 +144,9 @@ public class DefaultCalculatedFieldCache implements CalculatedFieldCache {
calculatedFieldFetchLock.lock(); calculatedFieldFetchLock.lock();
try { try {
CalculatedField calculatedField = calculatedFieldService.findById(tenantId, calculatedFieldId); CalculatedField calculatedField = calculatedFieldService.findById(tenantId, calculatedFieldId);
if (calculatedField == null) {
return;
}
EntityId cfEntityId = calculatedField.getEntityId(); EntityId cfEntityId = calculatedField.getEntityId();
calculatedFields.put(calculatedFieldId, calculatedField); calculatedFields.put(calculatedFieldId, calculatedField);

3
application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java

@ -44,6 +44,8 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import static org.thingsboard.common.util.ExpressionFunctionsUtil.userDefinedFunctions;
@Data @Data
public class CalculatedFieldCtx { public class CalculatedFieldCtx {
@ -111,6 +113,7 @@ public class CalculatedFieldCtx {
if (isValidExpression(expression)) { if (isValidExpression(expression)) {
this.customExpression = ThreadLocal.withInitial(() -> this.customExpression = ThreadLocal.withInitial(() ->
new ExpressionBuilder(expression) new ExpressionBuilder(expression)
.functions(userDefinedFunctions)
.implicitMultiplication(true) .implicitMultiplication(true)
.variables(this.arguments.keySet()) .variables(this.arguments.keySet())
.build() .build()

88
application/src/main/java/org/thingsboard/server/service/edge/EdgeMsgConstructorUtils.java

@ -16,6 +16,7 @@
package org.thingsboard.server.service.edge; package org.thingsboard.server.service.edge;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.JsonArray; import com.google.gson.JsonArray;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
@ -24,6 +25,11 @@ import com.google.gson.JsonPrimitive;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.action.TbSaveToCustomCassandraTableNode;
import org.thingsboard.rule.engine.aws.lambda.TbAwsLambdaNode;
import org.thingsboard.rule.engine.rest.TbSendRestApiCallReplyNode;
import org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode;
import org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode;
import org.thingsboard.server.common.adaptor.JsonConverter; import org.thingsboard.server.common.adaptor.JsonConverter;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.Dashboard;
@ -89,6 +95,7 @@ import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DeviceRpcCallMsg; import org.thingsboard.server.gen.edge.v1.DeviceRpcCallMsg;
import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg; import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg;
import org.thingsboard.server.gen.edge.v1.EdgeConfiguration; import org.thingsboard.server.gen.edge.v1.EdgeConfiguration;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.EntityDataProto; import org.thingsboard.server.gen.edge.v1.EntityDataProto;
import org.thingsboard.server.gen.edge.v1.EntityViewUpdateMsg; import org.thingsboard.server.gen.edge.v1.EntityViewUpdateMsg;
import org.thingsboard.server.gen.edge.v1.NotificationRuleUpdateMsg; import org.thingsboard.server.gen.edge.v1.NotificationRuleUpdateMsg;
@ -113,11 +120,36 @@ import org.thingsboard.server.gen.edge.v1.WidgetTypeUpdateMsg;
import org.thingsboard.server.gen.edge.v1.WidgetsBundleUpdateMsg; import org.thingsboard.server.gen.edge.v1.WidgetsBundleUpdateMsg;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
@Slf4j @Slf4j
public class EdgeMsgConstructorUtils { public class EdgeMsgConstructorUtils {
public static final Map<EdgeVersion, Map<String, String>> IGNORED_PARAMS_BY_EDGE_VERSION = Map.of(
EdgeVersion.V_3_8_0,
Map.of(
TbMsgTimeseriesNode.class.getName(), "processingSettings",
TbMsgAttributesNode.class.getName(), "processingSettings",
TbSaveToCustomCassandraTableNode.class.getName(), "defaultTtl"
),
EdgeVersion.V_3_7_0,
Map.of(
TbMsgTimeseriesNode.class.getName(), "processingSettings",
TbMsgAttributesNode.class.getName(), "processingSettings",
TbSaveToCustomCassandraTableNode.class.getName(), "defaultTtl"
)
);
public static final Map<EdgeVersion, Set<String>> EXCLUDED_NODES_BY_EDGE_VERSION = Map.of(
EdgeVersion.V_3_7_0,
Set.of(
TbSendRestApiCallReplyNode.class.getName(),
TbAwsLambdaNode.class.getName()
)
);
public static AlarmUpdateMsg constructAlarmUpdatedMsg(UpdateMsgType msgType, Alarm alarm) { public static AlarmUpdateMsg constructAlarmUpdatedMsg(UpdateMsgType msgType, Alarm alarm) {
return AlarmUpdateMsg.newBuilder().setMsgType(msgType) return AlarmUpdateMsg.newBuilder().setMsgType(msgType)
@ -417,8 +449,50 @@ public class EdgeMsgConstructorUtils {
.setIdLSB(ruleChainId.getId().getLeastSignificantBits()).build(); .setIdLSB(ruleChainId.getId().getLeastSignificantBits()).build();
} }
public static RuleChainMetadataUpdateMsg constructRuleChainMetadataUpdatedMsg(UpdateMsgType msgType, RuleChainMetaData ruleChainMetaData) { public static RuleChainMetadataUpdateMsg constructRuleChainMetadataUpdatedMsg(UpdateMsgType msgType, RuleChainMetaData ruleChainMetaData, EdgeVersion edgeVersion) {
return RuleChainMetadataUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(ruleChainMetaData)).build(); String metaData = sanitizeMetadataForLegacyEdgeVersion(ruleChainMetaData, edgeVersion);
return RuleChainMetadataUpdateMsg.newBuilder()
.setMsgType(msgType)
.setEntity(metaData)
.build();
}
private static String sanitizeMetadataForLegacyEdgeVersion(RuleChainMetaData ruleChainMetaData, EdgeVersion edgeVersion) {
JsonNode jsonNode = JacksonUtil.valueToTree(ruleChainMetaData);
JsonNode nodes = jsonNode.get("nodes");
updateNodeConfigurationsForLegacyEdge(nodes, edgeVersion);
removeExcludedNodesForLegacyEdge(nodes, edgeVersion);
return JacksonUtil.toString(jsonNode);
}
private static void updateNodeConfigurationsForLegacyEdge(JsonNode nodes, EdgeVersion edgeVersion) {
nodes.forEach(node -> {
if (node.isObject() && node.has("configuration")) {
String nodeType = node.get("type").asText();
Map<String, String> ignoredParams = IGNORED_PARAMS_BY_EDGE_VERSION.get(edgeVersion);
if (ignoredParams != null && ignoredParams.containsKey(nodeType)) {
((ObjectNode) node.get("configuration")).remove(ignoredParams.get(nodeType));
}
}
});
}
private static void removeExcludedNodesForLegacyEdge(JsonNode nodes, EdgeVersion edgeVersion) {
Iterator<JsonNode> iterator = nodes.iterator();
while (iterator.hasNext()) {
JsonNode node = iterator.next();
String type = node.get("type").asText();
Set<String> missNodes = EXCLUDED_NODES_BY_EDGE_VERSION.get(edgeVersion);
if (missNodes != null && missNodes.contains(type)) {
iterator.remove();
}
}
} }
public static EntityDataProto constructEntityDataMsg(TenantId tenantId, EntityId entityId, EdgeEventActionType actionType, JsonElement entityData) { public static EntityDataProto constructEntityDataMsg(TenantId tenantId, EntityId entityId, EdgeEventActionType actionType, JsonElement entityData) {
@ -433,7 +507,7 @@ public class EdgeMsgConstructorUtils {
JsonObject data = entityData.getAsJsonObject(); JsonObject data = entityData.getAsJsonObject();
builder.setPostTelemetryMsg(JsonConverter.convertToTelemetryProto(data.getAsJsonObject("data"), ts)); builder.setPostTelemetryMsg(JsonConverter.convertToTelemetryProto(data.getAsJsonObject("data"), ts));
} catch (Exception e) { } catch (Exception e) {
log.warn("[{}][{}] Can't convert to telemetry proto, entityData [{}]", tenantId, entityId, entityData, e); log.trace("[{}][{}] Can't convert to telemetry proto, entityData [{}]", tenantId, entityId, entityData, e);
} }
break; break;
case ATTRIBUTES_UPDATED: case ATTRIBUTES_UPDATED:
@ -448,7 +522,7 @@ public class EdgeMsgConstructorUtils {
builder.setPostAttributeScope(getScopeOfDefault(data)); builder.setPostAttributeScope(getScopeOfDefault(data));
builder.setAttributeTs(ts); builder.setAttributeTs(ts);
} catch (Exception e) { } catch (Exception e) {
log.warn("[{}][{}] Can't convert to AttributesUpdatedMsg proto, entityData [{}]", tenantId, entityId, entityData, e); log.trace("[{}][{}] Can't convert to AttributesUpdatedMsg proto, entityData [{}]", tenantId, entityId, entityData, e);
} }
break; break;
case POST_ATTRIBUTES: case POST_ATTRIBUTES:
@ -459,7 +533,7 @@ public class EdgeMsgConstructorUtils {
builder.setPostAttributeScope(getScopeOfDefault(data)); builder.setPostAttributeScope(getScopeOfDefault(data));
builder.setAttributeTs(ts); builder.setAttributeTs(ts);
} catch (Exception e) { } catch (Exception e) {
log.warn("[{}][{}] Can't convert to PostAttributesMsg, entityData [{}]", tenantId, entityId, entityData, e); log.trace("[{}][{}] Can't convert to PostAttributesMsg, entityData [{}]", tenantId, entityId, entityData, e);
} }
break; break;
case ATTRIBUTES_DELETED: case ATTRIBUTES_DELETED:
@ -467,12 +541,12 @@ public class EdgeMsgConstructorUtils {
AttributeDeleteMsg.Builder attributeDeleteMsg = AttributeDeleteMsg.newBuilder(); AttributeDeleteMsg.Builder attributeDeleteMsg = AttributeDeleteMsg.newBuilder();
attributeDeleteMsg.setScope(entityData.getAsJsonObject().getAsJsonPrimitive("scope").getAsString()); attributeDeleteMsg.setScope(entityData.getAsJsonObject().getAsJsonPrimitive("scope").getAsString());
JsonArray jsonArray = entityData.getAsJsonObject().getAsJsonArray("keys"); JsonArray jsonArray = entityData.getAsJsonObject().getAsJsonArray("keys");
List<String> keys = new Gson().fromJson(jsonArray.toString(), new TypeToken<>(){}.getType()); List<String> keys = new Gson().fromJson(jsonArray.toString(), new TypeToken<>() {}.getType());
attributeDeleteMsg.addAllAttributeNames(keys); attributeDeleteMsg.addAllAttributeNames(keys);
attributeDeleteMsg.build(); attributeDeleteMsg.build();
builder.setAttributeDeleteMsg(attributeDeleteMsg); builder.setAttributeDeleteMsg(attributeDeleteMsg);
} catch (Exception e) { } catch (Exception e) {
log.warn("[{}][{}] Can't convert to AttributeDeleteMsg proto, entityData [{}]", tenantId, entityId, entityData, e); log.trace("[{}][{}] Can't convert to AttributeDeleteMsg proto, entityData [{}]", tenantId, entityId, entityData, e);
} }
break; break;
} }

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

@ -203,7 +203,7 @@ public abstract class EdgeGrpcSession implements Closeable {
@Override @Override
public void onError(Throwable t) { public void onError(Throwable t) {
log.error("[{}][{}] Stream was terminated due to error:", tenantId, sessionId, t); log.trace("[{}][{}] Stream was terminated due to error:", tenantId, sessionId, t);
closeSession(); closeSession();
} }
@ -255,7 +255,7 @@ public abstract class EdgeGrpcSession implements Closeable {
private void doSync(EdgeSyncCursor cursor) { private void doSync(EdgeSyncCursor cursor) {
if (cursor.hasNext()) { if (cursor.hasNext()) {
EdgeEventFetcher next = cursor.getNext(); EdgeEventFetcher next = cursor.getNext();
log.info("[{}][{}] starting sync process, cursor current idx = {}, class = {}", log.debug("[{}][{}] starting sync process, cursor current idx = {}, class = {}",
tenantId, edge.getId(), cursor.getCurrentIdx(), next.getClass().getSimpleName()); tenantId, edge.getId(), cursor.getCurrentIdx(), next.getClass().getSimpleName());
ListenableFuture<Pair<Long, Long>> future = startProcessingEdgeEvents(next); ListenableFuture<Pair<Long, Long>> future = startProcessingEdgeEvents(next);
Futures.addCallback(future, new FutureCallback<>() { Futures.addCallback(future, new FutureCallback<>() {
@ -651,7 +651,7 @@ public abstract class EdgeGrpcSession implements Closeable {
default -> log.warn("[{}][{}] Unsupported action type [{}]", tenantId, sessionId, edgeEvent.getAction()); default -> log.warn("[{}][{}] Unsupported action type [{}]", tenantId, sessionId, edgeEvent.getAction());
} }
} catch (Exception e) { } catch (Exception e) {
log.error("[{}][{}] Exception during converting edge event to downlink msg", tenantId, sessionId, e); log.trace("[{}][{}] Exception during converting edge event to downlink msg", tenantId, sessionId, e);
} }
if (downlinkMsg != null) { if (downlinkMsg != null) {
result.add(downlinkMsg); result.add(downlinkMsg);
@ -763,7 +763,7 @@ public abstract class EdgeGrpcSession implements Closeable {
try { try {
outputStream.onNext(responseMsg); outputStream.onNext(responseMsg);
} catch (Exception e) { } catch (Exception e) {
log.error("[{}][{}] Failed to send downlink message [{}]", tenantId, sessionId, downlinkMsgStr, e); log.trace("[{}][{}] Failed to send downlink message [{}]", tenantId, sessionId, downlinkMsgStr, e);
connected = false; connected = false;
sessionCloseListener.accept(edge, sessionId); sessionCloseListener.accept(edge, sessionId);
} finally { } finally {
@ -780,7 +780,7 @@ public abstract class EdgeGrpcSession implements Closeable {
return null; return null;
} }
return ctx.getProcessor(edgeEvent.getType()).convertEdgeEventToDownlink(edgeEvent); return ctx.getProcessor(edgeEvent.getType()).convertEdgeEventToDownlink(edgeEvent, edgeVersion);
} }
public void addEventToHighPriorityQueue(EdgeEvent edgeEvent) { public void addEventToHighPriorityQueue(EdgeEvent edgeEvent) {
@ -909,7 +909,7 @@ public abstract class EdgeGrpcSession implements Closeable {
} }
} catch (Exception e) { } catch (Exception e) {
String failureMsg = String.format("Can't process uplink msg [%s] from edge", uplinkMsg); String failureMsg = String.format("Can't process uplink msg [%s] from edge", uplinkMsg);
log.error("[{}][{}] Can't process uplink msg [{}]", edge.getTenantId(), sessionId, uplinkMsg, e); log.trace("[{}][{}] Can't process uplink msg [{}]", edge.getTenantId(), sessionId, uplinkMsg, e);
ctx.getRuleProcessor().process(EdgeCommunicationFailureTrigger.builder().tenantId(edge.getTenantId()).edgeId(edge.getId()) ctx.getRuleProcessor().process(EdgeCommunicationFailureTrigger.builder().tenantId(edge.getTenantId()).edgeId(edge.getId())
.customerId(edge.getCustomerId()).edgeName(edge.getName()).failureMsg(failureMsg).error(e.getMessage()).build()); .customerId(edge.getCustomerId()).edgeName(edge.getName()).failureMsg(failureMsg).error(e.getMessage()).build());
return Futures.immediateFailedFuture(e); return Futures.immediateFailedFuture(e);

6
application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java

@ -65,8 +65,10 @@ public class EdgeSyncCursor {
fetchers.add(new AdminSettingsEdgeEventFetcher(ctx.getAdminSettingsService())); fetchers.add(new AdminSettingsEdgeEventFetcher(ctx.getAdminSettingsService()));
fetchers.add(new TenantAdminUsersEdgeEventFetcher(ctx.getUserService())); fetchers.add(new TenantAdminUsersEdgeEventFetcher(ctx.getUserService()));
} }
Customer publicCustomer = ctx.getCustomerService().findOrCreatePublicCustomer(edge.getTenantId()); Customer publicCustomer = ctx.getCustomerService().findPublicCustomer(edge.getTenantId());
fetchers.add(new CustomerEdgeEventFetcher(publicCustomer.getId())); if (publicCustomer != null) {
fetchers.add(new CustomerEdgeEventFetcher(publicCustomer.getId()));
}
if (edge.getCustomerId() != null && !EntityId.NULL_UUID.equals(edge.getCustomerId().getId())) { if (edge.getCustomerId() != null && !EntityId.NULL_UUID.equals(edge.getCustomerId().getId())) {
fetchers.add(new CustomerEdgeEventFetcher(edge.getCustomerId())); fetchers.add(new CustomerEdgeEventFetcher(edge.getCustomerId()));
fetchers.add(new CustomerUsersEdgeEventFetcher(ctx.getUserService(), edge.getCustomerId())); fetchers.add(new CustomerUsersEdgeEventFetcher(ctx.getUserService(), edge.getCustomerId()));

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EdgeProcessor.java

@ -20,13 +20,14 @@ import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
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.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
public interface EdgeProcessor { public interface EdgeProcessor {
ListenableFuture<Void> processEntityNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto msg); ListenableFuture<Void> processEntityNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto msg);
default DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { default DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
return null; return null;
} }

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

@ -30,6 +30,7 @@ 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.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.UpdateMsgType; 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.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
@ -55,7 +56,7 @@ public class AlarmEdgeProcessor extends BaseAlarmProcessor implements AlarmProce
} }
@Override @Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
AlarmUpdateMsg alarmUpdateMsg = convertAlarmEventToAlarmMsg(edgeEvent); AlarmUpdateMsg alarmUpdateMsg = convertAlarmEventToAlarmMsg(edgeEvent);
if (alarmUpdateMsg != null) { if (alarmUpdateMsg != null) {
return DownlinkMsg.newBuilder() return DownlinkMsg.newBuilder()

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/comment/AlarmCommentEdgeProcessor.java

@ -31,6 +31,7 @@ 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.AlarmCommentUpdateMsg;
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.UpdateMsgType; 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.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
@ -57,7 +58,7 @@ public class AlarmCommentEdgeProcessor extends BaseAlarmProcessor implements Ala
} }
@Override @Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction());
switch (edgeEvent.getAction()) { switch (edgeEvent.getAction()) {
case ADDED_COMMENT: case ADDED_COMMENT:

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessor.java

@ -36,6 +36,7 @@ import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg; import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg;
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.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.edge.EdgeMsgConstructorUtils; import org.thingsboard.server.service.edge.EdgeMsgConstructorUtils;
@ -107,7 +108,7 @@ public class AssetEdgeProcessor extends BaseAssetProcessor implements AssetProce
} }
@Override @Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
AssetId assetId = new AssetId(edgeEvent.getEntityId()); AssetId assetId = new AssetId(edgeEvent.getEntityId());
switch (edgeEvent.getAction()) { switch (edgeEvent.getAction()) {
case ADDED, UPDATED, ASSIGNED_TO_EDGE, ASSIGNED_TO_CUSTOMER, UNASSIGNED_FROM_CUSTOMER -> { case ADDED, UPDATED, ASSIGNED_TO_EDGE, ASSIGNED_TO_CUSTOMER, UNASSIGNED_FROM_CUSTOMER -> {

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/AssetProfileEdgeProcessor.java

@ -36,6 +36,7 @@ import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg; import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg;
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.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.edge.EdgeMsgConstructorUtils; import org.thingsboard.server.service.edge.EdgeMsgConstructorUtils;
@ -94,7 +95,7 @@ public class AssetProfileEdgeProcessor extends BaseAssetProfileProcessor impleme
} }
@Override @Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
AssetProfileId assetProfileId = new AssetProfileId(edgeEvent.getEntityId()); AssetProfileId assetProfileId = new AssetProfileId(edgeEvent.getEntityId());
switch (edgeEvent.getAction()) { switch (edgeEvent.getAction()) {
case ADDED, UPDATED -> { case ADDED, UPDATED -> {

28
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/customer/CustomerEdgeProcessor.java

@ -32,6 +32,7 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageDataIterable; import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.gen.edge.v1.CustomerUpdateMsg; import org.thingsboard.server.gen.edge.v1.CustomerUpdateMsg;
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.UpdateMsgType; 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.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
@ -48,7 +49,7 @@ import java.util.UUID;
public class CustomerEdgeProcessor extends BaseEdgeProcessor { public class CustomerEdgeProcessor extends BaseEdgeProcessor {
@Override @Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
CustomerId customerId = new CustomerId(edgeEvent.getEntityId()); CustomerId customerId = new CustomerId(edgeEvent.getEntityId());
switch (edgeEvent.getAction()) { switch (edgeEvent.getAction()) {
case ADDED, UPDATED -> { case ADDED, UPDATED -> {
@ -80,13 +81,16 @@ public class CustomerEdgeProcessor extends BaseEdgeProcessor {
UUID uuid = new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB()); UUID uuid = new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB());
CustomerId customerId = new CustomerId(EntityIdFactory.getByEdgeEventTypeAndUuid(type, uuid).getId()); CustomerId customerId = new CustomerId(EntityIdFactory.getByEdgeEventTypeAndUuid(type, uuid).getId());
switch (actionType) { switch (actionType) {
case UPDATED: case ADDED:
List<ListenableFuture<Void>> futures = new ArrayList<>(); Customer customerById = edgeCtx.getCustomerService().findCustomerById(tenantId, customerId);
PageDataIterable<Edge> edges = new PageDataIterable<>(link -> edgeCtx.getEdgeService().findEdgesByTenantIdAndCustomerId(tenantId, customerId, link), 1024); if (customerById != null && customerById.isPublic()) {
for (Edge edge : edges) { return findEdgesAndSaveEdgeEvents(link -> edgeCtx.getEdgeService().findEdgesByTenantId(tenantId, link),
futures.add(saveEdgeEvent(tenantId, edge.getId(), type, actionType, customerId, null)); tenantId, type, actionType, customerId);
} }
return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService); return Futures.immediateFuture(null);
case UPDATED:
return findEdgesAndSaveEdgeEvents(link -> edgeCtx.getEdgeService().findEdgesByTenantIdAndCustomerId(tenantId, customerId, link),
tenantId, type, actionType, customerId);
case DELETED: case DELETED:
EdgeId edgeId = new EdgeId(new UUID(edgeNotificationMsg.getEdgeIdMSB(), edgeNotificationMsg.getEdgeIdLSB())); EdgeId edgeId = new EdgeId(new UUID(edgeNotificationMsg.getEdgeIdMSB(), edgeNotificationMsg.getEdgeIdLSB()));
return saveEdgeEvent(tenantId, edgeId, type, actionType, customerId, null); return saveEdgeEvent(tenantId, edgeId, type, actionType, customerId, null);
@ -95,6 +99,16 @@ public class CustomerEdgeProcessor extends BaseEdgeProcessor {
} }
} }
public ListenableFuture<Void> findEdgesAndSaveEdgeEvents(PageDataIterable.FetchFunction<Edge> edgeFetcher, TenantId tenantId,
EdgeEventType type, EdgeEventActionType actionType, CustomerId customerId) {
List<ListenableFuture<Void>> futures = new ArrayList<>();
PageDataIterable<Edge> edges = new PageDataIterable<>(edgeFetcher, 1024);
for (Edge edge : edges) {
futures.add(saveEdgeEvent(tenantId, edge.getId(), type, actionType, customerId, null));
}
return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService);
}
@Override @Override
public EdgeEventType getEdgeEventType() { public EdgeEventType getEdgeEventType() {
return EdgeEventType.CUSTOMER; return EdgeEventType.CUSTOMER;

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardEdgeProcessor.java

@ -33,6 +33,7 @@ import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.gen.edge.v1.DashboardUpdateMsg; import org.thingsboard.server.gen.edge.v1.DashboardUpdateMsg;
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.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.edge.EdgeMsgConstructorUtils; import org.thingsboard.server.service.edge.EdgeMsgConstructorUtils;
@ -100,7 +101,7 @@ public class DashboardEdgeProcessor extends BaseDashboardProcessor implements Da
} }
@Override @Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
DashboardId dashboardId = new DashboardId(edgeEvent.getEntityId()); DashboardId dashboardId = new DashboardId(edgeEvent.getEntityId());
switch (edgeEvent.getAction()) { switch (edgeEvent.getAction()) {
case ADDED, UPDATED, ASSIGNED_TO_EDGE, ASSIGNED_TO_CUSTOMER, UNASSIGNED_FROM_CUSTOMER -> { case ADDED, UPDATED, ASSIGNED_TO_EDGE, ASSIGNED_TO_CUSTOMER, UNASSIGNED_FROM_CUSTOMER -> {

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java

@ -49,6 +49,7 @@ import org.thingsboard.server.gen.edge.v1.DeviceCredentialsUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DeviceRpcCallMsg; import org.thingsboard.server.gen.edge.v1.DeviceRpcCallMsg;
import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg; import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg;
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.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.TbQueueCallback;
import org.thingsboard.server.queue.TbQueueMsgMetadata; import org.thingsboard.server.queue.TbQueueMsgMetadata;
@ -222,7 +223,7 @@ public class DeviceEdgeProcessor extends BaseDeviceProcessor implements DevicePr
} }
@Override @Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
DeviceId deviceId = new DeviceId(edgeEvent.getEntityId()); DeviceId deviceId = new DeviceId(edgeEvent.getEntityId());
switch (edgeEvent.getAction()) { switch (edgeEvent.getAction()) {
case ADDED: case ADDED:

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/DeviceProfileEdgeProcessor.java

@ -36,6 +36,7 @@ import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg; import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg;
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.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.edge.EdgeMsgConstructorUtils; import org.thingsboard.server.service.edge.EdgeMsgConstructorUtils;
@ -94,7 +95,7 @@ public class DeviceProfileEdgeProcessor extends BaseDeviceProfileProcessor imple
} }
@Override @Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
DeviceProfileId deviceProfileId = new DeviceProfileId(edgeEvent.getEntityId()); DeviceProfileId deviceProfileId = new DeviceProfileId(edgeEvent.getEntityId());
switch (edgeEvent.getAction()) { switch (edgeEvent.getAction()) {
case ADDED, UPDATED -> { case ADDED, UPDATED -> {

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/edge/EdgeEntityProcessor.java

@ -32,6 +32,7 @@ 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.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.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.edge.EdgeMsgConstructorUtils; import org.thingsboard.server.service.edge.EdgeMsgConstructorUtils;
@ -101,7 +102,7 @@ public class EdgeEntityProcessor extends BaseEdgeProcessor {
} }
@Override @Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
EdgeId edgeId = new EdgeId(edgeEvent.getEntityId()); EdgeId edgeId = new EdgeId(edgeEvent.getEntityId());
switch (edgeEvent.getAction()) { switch (edgeEvent.getAction()) {
case ASSIGNED_TO_CUSTOMER, UNASSIGNED_FROM_CUSTOMER -> { case ASSIGNED_TO_CUSTOMER, UNASSIGNED_FROM_CUSTOMER -> {

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewEdgeProcessor.java

@ -34,6 +34,7 @@ import org.thingsboard.server.common.data.msg.TbMsgType;
import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
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.EntityViewUpdateMsg; import org.thingsboard.server.gen.edge.v1.EntityViewUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
@ -106,7 +107,7 @@ public class EntityViewEdgeProcessor extends BaseEntityViewProcessor implements
} }
@Override @Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
EntityViewId entityViewId = new EntityViewId(edgeEvent.getEntityId()); EntityViewId entityViewId = new EntityViewId(edgeEvent.getEntityId());
switch (edgeEvent.getAction()) { switch (edgeEvent.getAction()) {
case ADDED, UPDATED, ASSIGNED_TO_EDGE, ASSIGNED_TO_CUSTOMER, UNASSIGNED_FROM_CUSTOMER -> { case ADDED, UPDATED, ASSIGNED_TO_EDGE, ASSIGNED_TO_CUSTOMER, UNASSIGNED_FROM_CUSTOMER -> {

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/notification/NotificationRuleEdgeProcessor.java

@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.notification.rule.NotificationRule; import org.thingsboard.server.common.data.notification.rule.NotificationRule;
import org.thingsboard.server.dao.notification.NotificationRuleService; import org.thingsboard.server.dao.notification.NotificationRuleService;
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.NotificationRuleUpdateMsg; import org.thingsboard.server.gen.edge.v1.NotificationRuleUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
@ -40,7 +41,7 @@ public class NotificationRuleEdgeProcessor extends BaseEdgeProcessor {
private NotificationRuleService notificationRuleService; private NotificationRuleService notificationRuleService;
@Override @Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
NotificationRuleId notificationRuleId = new NotificationRuleId(edgeEvent.getEntityId()); NotificationRuleId notificationRuleId = new NotificationRuleId(edgeEvent.getEntityId());
DownlinkMsg downlinkMsg = null; DownlinkMsg downlinkMsg = null;
switch (edgeEvent.getAction()) { switch (edgeEvent.getAction()) {

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/notification/NotificationTargetEdgeProcessor.java

@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.dao.notification.NotificationTargetService; import org.thingsboard.server.dao.notification.NotificationTargetService;
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.NotificationTargetUpdateMsg; import org.thingsboard.server.gen.edge.v1.NotificationTargetUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
@ -40,7 +41,7 @@ public class NotificationTargetEdgeProcessor extends BaseEdgeProcessor {
private NotificationTargetService notificationTargetService; private NotificationTargetService notificationTargetService;
@Override @Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
NotificationTargetId notificationTargetId = new NotificationTargetId(edgeEvent.getEntityId()); NotificationTargetId notificationTargetId = new NotificationTargetId(edgeEvent.getEntityId());
DownlinkMsg downlinkMsg = null; DownlinkMsg downlinkMsg = null;
switch (edgeEvent.getAction()) { switch (edgeEvent.getAction()) {

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/notification/NotificationTemplateEdgeProcessor.java

@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.id.NotificationTemplateId;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate; import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
import org.thingsboard.server.dao.notification.NotificationTemplateService; import org.thingsboard.server.dao.notification.NotificationTemplateService;
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.NotificationTemplateUpdateMsg; import org.thingsboard.server.gen.edge.v1.NotificationTemplateUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
@ -40,7 +41,7 @@ public class NotificationTemplateEdgeProcessor extends BaseEdgeProcessor {
private NotificationTemplateService notificationTemplateService; private NotificationTemplateService notificationTemplateService;
@Override @Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
NotificationTemplateId notificationTemplateId = new NotificationTemplateId(edgeEvent.getEntityId()); NotificationTemplateId notificationTemplateId = new NotificationTemplateId(edgeEvent.getEntityId());
DownlinkMsg downlinkMsg = null; DownlinkMsg downlinkMsg = null;
switch (edgeEvent.getAction()) { switch (edgeEvent.getAction()) {

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/oauth2/DomainEdgeProcessor.java

@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.id.DomainId;
import org.thingsboard.server.common.data.oauth2.OAuth2Client; import org.thingsboard.server.common.data.oauth2.OAuth2Client;
import org.thingsboard.server.dao.domain.DomainService; import org.thingsboard.server.dao.domain.DomainService;
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.OAuth2ClientUpdateMsg; import org.thingsboard.server.gen.edge.v1.OAuth2ClientUpdateMsg;
import org.thingsboard.server.gen.edge.v1.OAuth2DomainUpdateMsg; import org.thingsboard.server.gen.edge.v1.OAuth2DomainUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
@ -42,7 +43,7 @@ public class DomainEdgeProcessor extends BaseEdgeProcessor {
private DomainService domainService; private DomainService domainService;
@Override @Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
DomainId domainId = new DomainId(edgeEvent.getEntityId()); DomainId domainId = new DomainId(edgeEvent.getEntityId());
switch (edgeEvent.getAction()) { switch (edgeEvent.getAction()) {
case ADDED, UPDATED -> { case ADDED, UPDATED -> {

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/oauth2/OAuth2ClientEdgeProcessor.java

@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.OAuth2ClientId; import org.thingsboard.server.common.data.id.OAuth2ClientId;
import org.thingsboard.server.common.data.oauth2.OAuth2Client; import org.thingsboard.server.common.data.oauth2.OAuth2Client;
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.OAuth2ClientUpdateMsg; import org.thingsboard.server.gen.edge.v1.OAuth2ClientUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
@ -35,7 +36,7 @@ import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
public class OAuth2ClientEdgeProcessor extends BaseEdgeProcessor { public class OAuth2ClientEdgeProcessor extends BaseEdgeProcessor {
@Override @Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
OAuth2ClientId oAuth2ClientId = new OAuth2ClientId(edgeEvent.getEntityId()); OAuth2ClientId oAuth2ClientId = new OAuth2ClientId(edgeEvent.getEntityId());
switch (edgeEvent.getAction()) { switch (edgeEvent.getAction()) {

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/ota/OtaPackageEdgeProcessor.java

@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.OtaPackageId; import org.thingsboard.server.common.data.id.OtaPackageId;
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.OtaPackageUpdateMsg; import org.thingsboard.server.gen.edge.v1.OtaPackageUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
@ -35,7 +36,7 @@ import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
public class OtaPackageEdgeProcessor extends BaseEdgeProcessor { public class OtaPackageEdgeProcessor extends BaseEdgeProcessor {
@Override @Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
OtaPackageId otaPackageId = new OtaPackageId(edgeEvent.getEntityId()); OtaPackageId otaPackageId = new OtaPackageId(edgeEvent.getEntityId());
switch (edgeEvent.getAction()) { switch (edgeEvent.getAction()) {
case ADDED, UPDATED -> { case ADDED, UPDATED -> {

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/queue/QueueEdgeProcessor.java

@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.QueueId; import org.thingsboard.server.common.data.id.QueueId;
import org.thingsboard.server.common.data.queue.Queue; import org.thingsboard.server.common.data.queue.Queue;
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.QueueUpdateMsg; import org.thingsboard.server.gen.edge.v1.QueueUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
@ -35,7 +36,7 @@ import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
public class QueueEdgeProcessor extends BaseEdgeProcessor { public class QueueEdgeProcessor extends BaseEdgeProcessor {
@Override @Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
QueueId queueId = new QueueId(edgeEvent.getEntityId()); QueueId queueId = new QueueId(edgeEvent.getEntityId());
switch (edgeEvent.getAction()) { switch (edgeEvent.getAction()) {
case ADDED, UPDATED -> { case ADDED, UPDATED -> {

4
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessor.java

@ -21,7 +21,6 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
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.EntityType;
import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.Edge;
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;
@ -30,6 +29,7 @@ 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.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelation;
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.RelationUpdateMsg; import org.thingsboard.server.gen.edge.v1.RelationUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
@ -85,7 +85,7 @@ public class RelationEdgeProcessor extends BaseRelationProcessor implements Rela
} }
@Override @Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
EntityRelation entityRelation = JacksonUtil.convertValue(edgeEvent.getBody(), EntityRelation.class); EntityRelation entityRelation = JacksonUtil.convertValue(edgeEvent.getBody(), EntityRelation.class);
UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction());
RelationUpdateMsg relationUpdateMsg = EdgeMsgConstructorUtils.constructRelationUpdatedMsg(msgType, entityRelation); RelationUpdateMsg relationUpdateMsg = EdgeMsgConstructorUtils.constructRelationUpdatedMsg(msgType, entityRelation);

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessor.java

@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.id.TbResourceId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
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.ResourceUpdateMsg; import org.thingsboard.server.gen.edge.v1.ResourceUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
@ -73,7 +74,7 @@ public class ResourceEdgeProcessor extends BaseResourceProcessor implements Reso
} }
@Override @Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
TbResourceId tbResourceId = new TbResourceId(edgeEvent.getEntityId()); TbResourceId tbResourceId = new TbResourceId(edgeEvent.getEntityId());
switch (edgeEvent.getAction()) { switch (edgeEvent.getAction()) {
case ADDED, UPDATED -> { case ADDED, UPDATED -> {

5
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainEdgeProcessor.java

@ -33,6 +33,7 @@ import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
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.RuleChainMetadataUpdateMsg; import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg;
import org.thingsboard.server.gen.edge.v1.RuleChainUpdateMsg; import org.thingsboard.server.gen.edge.v1.RuleChainUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
@ -134,7 +135,7 @@ public class RuleChainEdgeProcessor extends BaseRuleChainProcessor {
} }
@Override @Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
RuleChainId ruleChainId = new RuleChainId(edgeEvent.getEntityId()); RuleChainId ruleChainId = new RuleChainId(edgeEvent.getEntityId());
DownlinkMsg downlinkMsg = null; DownlinkMsg downlinkMsg = null;
switch (edgeEvent.getAction()) { switch (edgeEvent.getAction()) {
@ -161,7 +162,7 @@ public class RuleChainEdgeProcessor extends BaseRuleChainProcessor {
RuleChainMetaData ruleChainMetaData = edgeCtx.getRuleChainService().loadRuleChainMetaData(edgeEvent.getTenantId(), ruleChainId); RuleChainMetaData ruleChainMetaData = edgeCtx.getRuleChainService().loadRuleChainMetaData(edgeEvent.getTenantId(), ruleChainId);
RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg = EdgeMsgConstructorUtils RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg = EdgeMsgConstructorUtils
.constructRuleChainMetadataUpdatedMsg(msgType, ruleChainMetaData); .constructRuleChainMetadataUpdatedMsg(msgType, ruleChainMetaData, edgeVersion);
builder.addRuleChainMetadataUpdateMsg(ruleChainMetadataUpdateMsg); builder.addRuleChainMetadataUpdateMsg(ruleChainMetadataUpdateMsg);
downlinkMsg = builder.build(); downlinkMsg = builder.build();
} }

5
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainMetadataEdgeProcessor.java

@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleChainMetaData;
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.RuleChainMetadataUpdateMsg; import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
@ -36,14 +37,14 @@ import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
public class RuleChainMetadataEdgeProcessor extends BaseEdgeProcessor { public class RuleChainMetadataEdgeProcessor extends BaseEdgeProcessor {
@Override @Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
RuleChainId ruleChainId = new RuleChainId(edgeEvent.getEntityId()); RuleChainId ruleChainId = new RuleChainId(edgeEvent.getEntityId());
RuleChain ruleChain = edgeCtx.getRuleChainService().findRuleChainById(edgeEvent.getTenantId(), ruleChainId); RuleChain ruleChain = edgeCtx.getRuleChainService().findRuleChainById(edgeEvent.getTenantId(), ruleChainId);
if (ruleChain != null) { if (ruleChain != null) {
RuleChainMetaData ruleChainMetaData = edgeCtx.getRuleChainService().loadRuleChainMetaData(edgeEvent.getTenantId(), ruleChainId); RuleChainMetaData ruleChainMetaData = edgeCtx.getRuleChainService().loadRuleChainMetaData(edgeEvent.getTenantId(), ruleChainId);
UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction());
RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg = EdgeMsgConstructorUtils RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg = EdgeMsgConstructorUtils
.constructRuleChainMetadataUpdatedMsg(msgType, ruleChainMetaData); .constructRuleChainMetadataUpdatedMsg(msgType, ruleChainMetaData, edgeVersion);
return DownlinkMsg.newBuilder() return DownlinkMsg.newBuilder()
.setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .setDownlinkMsgId(EdgeUtils.nextPositiveInt())
.addRuleChainMetadataUpdateMsg(ruleChainMetadataUpdateMsg) .addRuleChainMetadataUpdateMsg(ruleChainMetadataUpdateMsg)

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/settings/AdminSettingsEdgeProcessor.java

@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.gen.edge.v1.AdminSettingsUpdateMsg; import org.thingsboard.server.gen.edge.v1.AdminSettingsUpdateMsg;
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.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
@ -33,7 +34,7 @@ import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
public class AdminSettingsEdgeProcessor extends BaseEdgeProcessor { public class AdminSettingsEdgeProcessor extends BaseEdgeProcessor {
@Override @Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
AdminSettings adminSettings = JacksonUtil.convertValue(edgeEvent.getBody(), AdminSettings.class); AdminSettings adminSettings = JacksonUtil.convertValue(edgeEvent.getBody(), AdminSettings.class);
if (adminSettings == null) { if (adminSettings == null) {
return null; return null;

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantEdgeProcessor.java

@ -25,6 +25,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.TenantId; import org.thingsboard.server.common.data.id.TenantId;
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.TenantProfileUpdateMsg; import org.thingsboard.server.gen.edge.v1.TenantProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.TenantUpdateMsg; import org.thingsboard.server.gen.edge.v1.TenantUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
@ -38,7 +39,7 @@ import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
public class TenantEdgeProcessor extends BaseEdgeProcessor { public class TenantEdgeProcessor extends BaseEdgeProcessor {
@Override @Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
TenantId tenantId = TenantId.fromUUID(edgeEvent.getEntityId()); TenantId tenantId = TenantId.fromUUID(edgeEvent.getEntityId());
if (EdgeEventActionType.UPDATED.equals(edgeEvent.getAction())) { if (EdgeEventActionType.UPDATED.equals(edgeEvent.getAction())) {
Tenant tenant = edgeCtx.getTenantService().findTenantById(tenantId); Tenant tenant = edgeCtx.getTenantService().findTenantById(tenantId);

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantProfileEdgeProcessor.java

@ -24,6 +24,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.TenantProfileId; import org.thingsboard.server.common.data.id.TenantProfileId;
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.TenantProfileUpdateMsg; import org.thingsboard.server.gen.edge.v1.TenantProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
@ -36,7 +37,7 @@ import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
public class TenantProfileEdgeProcessor extends BaseEdgeProcessor { public class TenantProfileEdgeProcessor extends BaseEdgeProcessor {
@Override @Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
TenantProfileId tenantProfileId = new TenantProfileId(edgeEvent.getEntityId()); TenantProfileId tenantProfileId = new TenantProfileId(edgeEvent.getEntityId());
if (EdgeEventActionType.UPDATED.equals(edgeEvent.getAction())) { if (EdgeEventActionType.UPDATED.equals(edgeEvent.getAction())) {
TenantProfile tenantProfile = edgeCtx.getTenantProfileService().findTenantProfileById(edgeEvent.getTenantId(), tenantProfileId); TenantProfile tenantProfile = edgeCtx.getTenantProfileService().findTenantProfileById(edgeEvent.getTenantId(), tenantProfileId);

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/user/UserEdgeProcessor.java

@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.common.data.security.UserCredentials;
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.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.gen.edge.v1.UserCredentialsUpdateMsg; import org.thingsboard.server.gen.edge.v1.UserCredentialsUpdateMsg;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
@ -36,7 +37,7 @@ import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
public class UserEdgeProcessor extends BaseEdgeProcessor { public class UserEdgeProcessor extends BaseEdgeProcessor {
@Override @Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
UserId userId = new UserId(edgeEvent.getEntityId()); UserId userId = new UserId(edgeEvent.getEntityId());
switch (edgeEvent.getAction()) { switch (edgeEvent.getAction()) {
case ADDED, UPDATED -> { case ADDED, UPDATED -> {

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetBundleEdgeProcessor.java

@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.WidgetsBundleId; import org.thingsboard.server.common.data.id.WidgetsBundleId;
import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.common.data.widget.WidgetsBundle;
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.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.gen.edge.v1.WidgetsBundleUpdateMsg; import org.thingsboard.server.gen.edge.v1.WidgetsBundleUpdateMsg;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
@ -37,7 +38,7 @@ import java.util.List;
public class WidgetBundleEdgeProcessor extends BaseEdgeProcessor { public class WidgetBundleEdgeProcessor extends BaseEdgeProcessor {
@Override @Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
WidgetsBundleId widgetsBundleId = new WidgetsBundleId(edgeEvent.getEntityId()); WidgetsBundleId widgetsBundleId = new WidgetsBundleId(edgeEvent.getEntityId());
switch (edgeEvent.getAction()) { switch (edgeEvent.getAction()) {
case ADDED, UPDATED -> { case ADDED, UPDATED -> {

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetTypeEdgeProcessor.java

@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.id.WidgetTypeId;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails; import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
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.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.gen.edge.v1.WidgetTypeUpdateMsg; import org.thingsboard.server.gen.edge.v1.WidgetTypeUpdateMsg;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
@ -35,7 +36,7 @@ import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
public class WidgetTypeEdgeProcessor extends BaseEdgeProcessor { public class WidgetTypeEdgeProcessor extends BaseEdgeProcessor {
@Override @Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
WidgetTypeId widgetTypeId = new WidgetTypeId(edgeEvent.getEntityId()); WidgetTypeId widgetTypeId = new WidgetTypeId(edgeEvent.getEntityId());
switch (edgeEvent.getAction()) { switch (edgeEvent.getAction()) {
case ADDED, UPDATED -> { case ADDED, UPDATED -> {

1
application/src/main/java/org/thingsboard/server/service/edge/rpc/utils/EdgeVersionUtils.java

@ -24,4 +24,5 @@ public final class EdgeVersionUtils {
public static boolean isEdgeVersionOlderThan(EdgeVersion currentVersion, EdgeVersion requiredVersion) { public static boolean isEdgeVersionOlderThan(EdgeVersion currentVersion, EdgeVersion requiredVersion) {
return currentVersion.ordinal() < requiredVersion.ordinal(); return currentVersion.ordinal() < requiredVersion.ordinal();
} }
} }

44
application/src/main/java/org/thingsboard/server/service/edqs/EdqsSyncService.java

@ -161,13 +161,17 @@ public abstract class EdqsSyncService {
private void processRelationBatch(List<RelationEntity> relations) { private void processRelationBatch(List<RelationEntity> relations) {
for (RelationEntity relation : relations) { for (RelationEntity relation : relations) {
if (RelationTypeGroup.COMMON.name().equals(relation.getRelationTypeGroup())) { try {
EntityIdInfo entityIdInfo = entityInfoMap.get(relation.getFromId()); if (RelationTypeGroup.COMMON.name().equals(relation.getRelationTypeGroup())) {
if (entityIdInfo != null) { EntityIdInfo entityIdInfo = entityInfoMap.get(relation.getFromId());
process(entityIdInfo.tenantId(), RELATION, relation.toData()); if (entityIdInfo != null) {
} else { process(entityIdInfo.tenantId(), RELATION, relation.toData());
log.info("Relation from id not found: {} ", relation); } else {
log.info("Relation from id not found: {} ", relation);
}
} }
} catch (Exception e) {
log.error("Failed to sync relation: {}", relation, e);
} }
} }
} }
@ -207,19 +211,23 @@ public abstract class EdqsSyncService {
private void processAttributeBatch(List<AttributeKvEntity> batch) { private void processAttributeBatch(List<AttributeKvEntity> batch) {
for (AttributeKvEntity attribute : batch) { for (AttributeKvEntity attribute : batch) {
attribute.setStrKey(getStrKeyOrFetchFromDb(attribute.getId().getAttributeKey())); try {
UUID entityId = attribute.getId().getEntityId(); attribute.setStrKey(getStrKeyOrFetchFromDb(attribute.getId().getAttributeKey()));
EntityIdInfo entityIdInfo = entityInfoMap.get(entityId); UUID entityId = attribute.getId().getEntityId();
if (entityIdInfo == null) { EntityIdInfo entityIdInfo = entityInfoMap.get(entityId);
log.debug("Skipping attribute with entity UUID {} as it is not found in entityInfoMap", entityId); if (entityIdInfo == null) {
continue; log.debug("Skipping attribute with entity UUID {} as it is not found in entityInfoMap", entityId);
continue;
}
AttributeKv attributeKv = new AttributeKv(
EntityIdFactory.getByTypeAndUuid(entityIdInfo.entityType(), entityId),
AttributeScope.valueOf(attribute.getId().getAttributeType()),
attribute.toData(),
attribute.getVersion());
process(entityIdInfo.tenantId(), ATTRIBUTE_KV, attributeKv);
} catch (Exception e) {
log.error("Failed to sync attribute: {}", attribute, e);
} }
AttributeKv attributeKv = new AttributeKv(
EntityIdFactory.getByTypeAndUuid(entityIdInfo.entityType(), entityId),
AttributeScope.valueOf(attribute.getId().getAttributeType()),
attribute.toData(),
attribute.getVersion());
process(entityIdInfo.tenantId(), ATTRIBUTE_KV, attributeKv);
} }
} }

2
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCalculatedFieldConsumerService.java

@ -16,7 +16,6 @@
package org.thingsboard.server.service.queue; package org.thingsboard.server.service.queue;
import jakarta.annotation.PreDestroy; import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
@ -70,7 +69,6 @@ import java.util.stream.Collectors;
@Service @Service
@TbRuleEngineComponent @TbRuleEngineComponent
@Slf4j
public class DefaultTbCalculatedFieldConsumerService extends AbstractPartitionBasedConsumerService<ToCalculatedFieldNotificationMsg> implements TbCalculatedFieldConsumerService { public class DefaultTbCalculatedFieldConsumerService extends AbstractPartitionBasedConsumerService<ToCalculatedFieldNotificationMsg> implements TbCalculatedFieldConsumerService {
@Value("${queue.calculated_fields.poll_interval:25}") @Value("${queue.calculated_fields.poll_interval:25}")

14
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java

@ -551,11 +551,15 @@ public class DefaultTbClusterService implements TbClusterService {
} }
private void processEdgeNotification(EdgeId edgeId, ToEdgeNotificationMsg toEdgeNotificationMsg) { private void processEdgeNotification(EdgeId edgeId, ToEdgeNotificationMsg toEdgeNotificationMsg) {
var serviceIdOpt = Optional.ofNullable(edgeIdServiceIdCache.get(edgeId)); if (edgesEnabled) {
serviceIdOpt.ifPresentOrElse( var serviceIdOpt = Optional.ofNullable(edgeIdServiceIdCache.get(edgeId));
serviceId -> pushMsgToEdgeNotification(toEdgeNotificationMsg, serviceId.get()), serviceIdOpt.ifPresentOrElse(
() -> broadcastEdgeNotification(edgeId, toEdgeNotificationMsg) serviceId -> pushMsgToEdgeNotification(toEdgeNotificationMsg, serviceId.get()),
); () -> broadcastEdgeNotification(edgeId, toEdgeNotificationMsg)
);
} else {
log.trace("Edges disabled. Ignoring edge notification {} for edgeId: {}", toEdgeNotificationMsg, edgeId);
}
} }
private void pushMsgToEdgeNotification(ToEdgeNotificationMsg toEdgeNotificationMsg, String serviceId) { private void pushMsgToEdgeNotification(ToEdgeNotificationMsg toEdgeNotificationMsg, String serviceId) {

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

@ -20,7 +20,6 @@ import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy; import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
@ -118,7 +117,6 @@ import java.util.stream.Collectors;
@Service @Service
@TbCoreComponent @TbCoreComponent
@Slf4j
public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCoreNotificationMsg> implements TbCoreConsumerService { public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCoreNotificationMsg> implements TbCoreConsumerService {
@Value("${queue.core.poll-interval}") @Value("${queue.core.poll-interval}")

26
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbEdgeConsumerService.java

@ -19,8 +19,6 @@ import com.google.common.util.concurrent.FutureCallback;
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 com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
@ -45,12 +43,13 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeNotificationMsg;
import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.common.consumer.MainQueueConsumerManager;
import org.thingsboard.server.queue.discovery.QueueKey; import org.thingsboard.server.queue.discovery.QueueKey;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.queue.provider.TbCoreQueueFactory; import org.thingsboard.server.queue.provider.TbCoreQueueFactory;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.edge.EdgeContextComponent; import org.thingsboard.server.service.edge.EdgeContextComponent;
import org.thingsboard.server.queue.common.consumer.MainQueueConsumerManager; import org.thingsboard.server.service.edge.rpc.EdgeRpcService;
import org.thingsboard.server.service.queue.processing.AbstractConsumerService; import org.thingsboard.server.service.queue.processing.AbstractConsumerService;
import org.thingsboard.server.service.queue.processing.IdMsgPair; import org.thingsboard.server.service.queue.processing.IdMsgPair;
@ -65,7 +64,6 @@ import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Slf4j
@Service @Service
@TbCoreComponent @TbCoreComponent
public class DefaultTbEdgeConsumerService extends AbstractConsumerService<ToEdgeNotificationMsg> implements TbEdgeConsumerService { public class DefaultTbEdgeConsumerService extends AbstractConsumerService<ToEdgeNotificationMsg> implements TbEdgeConsumerService {
@ -195,36 +193,42 @@ public class DefaultTbEdgeConsumerService extends AbstractConsumerService<ToEdge
protected void handleNotification(UUID id, TbProtoQueueMsg<ToEdgeNotificationMsg> msg, TbCallback callback) { protected void handleNotification(UUID id, TbProtoQueueMsg<ToEdgeNotificationMsg> msg, TbCallback callback) {
ToEdgeNotificationMsg toEdgeNotificationMsg = msg.getValue(); ToEdgeNotificationMsg toEdgeNotificationMsg = msg.getValue();
try { try {
EdgeRpcService edgeRpcService = edgeCtx.getEdgeRpcService();
if (edgeRpcService == null) {
log.debug("No EdgeRpcService available (edge functionality disabled), ignoring msg: {}", toEdgeNotificationMsg);
callback.onSuccess();
return;
}
if (toEdgeNotificationMsg.hasEdgeHighPriority()) { if (toEdgeNotificationMsg.hasEdgeHighPriority()) {
EdgeSessionMsg edgeSessionMsg = ProtoUtils.fromProto(toEdgeNotificationMsg.getEdgeHighPriority()); EdgeSessionMsg edgeSessionMsg = ProtoUtils.fromProto(toEdgeNotificationMsg.getEdgeHighPriority());
edgeCtx.getEdgeRpcService().onToEdgeSessionMsg(edgeSessionMsg.getTenantId(), edgeSessionMsg); edgeRpcService.onToEdgeSessionMsg(edgeSessionMsg.getTenantId(), edgeSessionMsg);
callback.onSuccess(); callback.onSuccess();
} else if (toEdgeNotificationMsg.hasEdgeEventUpdate()) { } else if (toEdgeNotificationMsg.hasEdgeEventUpdate()) {
EdgeSessionMsg edgeSessionMsg = ProtoUtils.fromProto(toEdgeNotificationMsg.getEdgeEventUpdate()); EdgeSessionMsg edgeSessionMsg = ProtoUtils.fromProto(toEdgeNotificationMsg.getEdgeEventUpdate());
edgeCtx.getEdgeRpcService().onToEdgeSessionMsg(edgeSessionMsg.getTenantId(), edgeSessionMsg); edgeRpcService.onToEdgeSessionMsg(edgeSessionMsg.getTenantId(), edgeSessionMsg);
callback.onSuccess(); callback.onSuccess();
} else if (toEdgeNotificationMsg.hasToEdgeSyncRequest()) { } else if (toEdgeNotificationMsg.hasToEdgeSyncRequest()) {
EdgeSessionMsg edgeSessionMsg = ProtoUtils.fromProto(toEdgeNotificationMsg.getToEdgeSyncRequest()); EdgeSessionMsg edgeSessionMsg = ProtoUtils.fromProto(toEdgeNotificationMsg.getToEdgeSyncRequest());
edgeCtx.getEdgeRpcService().onToEdgeSessionMsg(edgeSessionMsg.getTenantId(), edgeSessionMsg); edgeRpcService.onToEdgeSessionMsg(edgeSessionMsg.getTenantId(), edgeSessionMsg);
callback.onSuccess(); callback.onSuccess();
} else if (toEdgeNotificationMsg.hasFromEdgeSyncResponse()) { } else if (toEdgeNotificationMsg.hasFromEdgeSyncResponse()) {
EdgeSessionMsg edgeSessionMsg = ProtoUtils.fromProto(toEdgeNotificationMsg.getFromEdgeSyncResponse()); EdgeSessionMsg edgeSessionMsg = ProtoUtils.fromProto(toEdgeNotificationMsg.getFromEdgeSyncResponse());
edgeCtx.getEdgeRpcService().onToEdgeSessionMsg(edgeSessionMsg.getTenantId(), edgeSessionMsg); edgeRpcService.onToEdgeSessionMsg(edgeSessionMsg.getTenantId(), edgeSessionMsg);
callback.onSuccess(); callback.onSuccess();
} else if (toEdgeNotificationMsg.hasComponentLifecycle()) { } else if (toEdgeNotificationMsg.hasComponentLifecycle()) {
ComponentLifecycleMsg componentLifecycle = ProtoUtils.fromProto(toEdgeNotificationMsg.getComponentLifecycle()); ComponentLifecycleMsg componentLifecycle = ProtoUtils.fromProto(toEdgeNotificationMsg.getComponentLifecycle());
TenantId tenantId = componentLifecycle.getTenantId(); TenantId tenantId = componentLifecycle.getTenantId();
EdgeId edgeId = new EdgeId(componentLifecycle.getEntityId().getId()); EdgeId edgeId = new EdgeId(componentLifecycle.getEntityId().getId());
if (ComponentLifecycleEvent.DELETED.equals(componentLifecycle.getEvent())) { if (ComponentLifecycleEvent.DELETED.equals(componentLifecycle.getEvent())) {
edgeCtx.getEdgeRpcService().deleteEdge(tenantId, edgeId); edgeRpcService.deleteEdge(tenantId, edgeId);
} else if (ComponentLifecycleEvent.UPDATED.equals(componentLifecycle.getEvent())) { } else if (ComponentLifecycleEvent.UPDATED.equals(componentLifecycle.getEvent())) {
Edge edge = edgeCtx.getEdgeService().findEdgeById(tenantId, edgeId); Edge edge = edgeCtx.getEdgeService().findEdgeById(tenantId, edgeId);
edgeCtx.getEdgeRpcService().updateEdge(tenantId, edge); edgeRpcService.updateEdge(tenantId, edge);
} }
callback.onSuccess(); callback.onSuccess();
} }
} catch (Exception e) { } catch (Exception e) {
log.error("Error processing edge notification message", e); log.error("Error processing edge notification message {}", toEdgeNotificationMsg, e);
callback.onFailure(e); callback.onFailure(e);
} }

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

@ -15,7 +15,6 @@
*/ */
package org.thingsboard.server.service.queue; package org.thingsboard.server.service.queue;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.event.EventListener; import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
@ -65,7 +64,6 @@ import java.util.stream.Collectors;
@Service @Service
@TbRuleEngineComponent @TbRuleEngineComponent
@Slf4j
public class DefaultTbRuleEngineConsumerService extends AbstractPartitionBasedConsumerService<ToRuleEngineNotificationMsg> implements TbRuleEngineConsumerService { public class DefaultTbRuleEngineConsumerService extends AbstractPartitionBasedConsumerService<ToRuleEngineNotificationMsg> implements TbRuleEngineConsumerService {
private final TbRuleEngineConsumerContext ctx; private final TbRuleEngineConsumerContext ctx;

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

@ -17,7 +17,8 @@ package org.thingsboard.server.service.queue.processing;
import jakarta.annotation.PreDestroy; import jakarta.annotation.PreDestroy;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.common.util.ThingsBoardThreadFactory;
@ -62,10 +63,11 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Slf4j
@RequiredArgsConstructor @RequiredArgsConstructor
public abstract class AbstractConsumerService<N extends com.google.protobuf.GeneratedMessageV3> extends TbApplicationEventListener<PartitionChangeEvent> { public abstract class AbstractConsumerService<N extends com.google.protobuf.GeneratedMessageV3> extends TbApplicationEventListener<PartitionChangeEvent> {
protected final Logger log = LoggerFactory.getLogger(getClass());
protected final ActorSystemContext actorContext; protected final ActorSystemContext actorContext;
protected final TbTenantProfileCache tenantProfileCache; protected final TbTenantProfileCache tenantProfileCache;
protected final TbDeviceProfileCache deviceProfileCache; protected final TbDeviceProfileCache deviceProfileCache;

19
application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractPartitionBasedConsumerService.java

@ -28,6 +28,8 @@ import org.thingsboard.server.service.profile.TbAssetProfileCache;
import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService; import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
@ -35,7 +37,7 @@ public abstract class AbstractPartitionBasedConsumerService<N extends com.google
private final Lock startupLock = new ReentrantLock(); private final Lock startupLock = new ReentrantLock();
private volatile boolean started = false; private volatile boolean started = false;
private PartitionChangeEvent lastPartitionChangeEvent; private List<PartitionChangeEvent> pendingEvents = new ArrayList<>();
public AbstractPartitionBasedConsumerService(ActorSystemContext actorContext, public AbstractPartitionBasedConsumerService(ActorSystemContext actorContext,
TbTenantProfileCache tenantProfileCache, TbTenantProfileCache tenantProfileCache,
@ -61,8 +63,16 @@ public abstract class AbstractPartitionBasedConsumerService<N extends com.google
onStartUp(); onStartUp();
startupLock.lock(); startupLock.lock();
try { try {
onPartitionChangeEvent(lastPartitionChangeEvent); for (PartitionChangeEvent partitionChangeEvent : pendingEvents) {
log.info("Handling partition change event: {}", partitionChangeEvent);
try {
onPartitionChangeEvent(partitionChangeEvent);
} catch (Throwable t) {
log.error("Failed to handle partition change event: {}", partitionChangeEvent, t);
}
}
started = true; started = true;
pendingEvents = null;
} finally { } finally {
startupLock.unlock(); startupLock.unlock();
} }
@ -70,17 +80,20 @@ public abstract class AbstractPartitionBasedConsumerService<N extends com.google
@Override @Override
protected void onTbApplicationEvent(PartitionChangeEvent event) { protected void onTbApplicationEvent(PartitionChangeEvent event) {
log.debug("Received partition change event: {}", event);
if (!started) { if (!started) {
startupLock.lock(); startupLock.lock();
try { try {
if (!started) { if (!started) {
lastPartitionChangeEvent = event; log.debug("App not started yet, storing event for later: {}", event);
pendingEvents.add(event);
return; return;
} }
} finally { } finally {
startupLock.unlock(); startupLock.unlock();
} }
} }
log.info("Handling partition change event: {}", event);
onPartitionChangeEvent(event); onPartitionChangeEvent(event);
} }

26
application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java

@ -343,15 +343,31 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer
s -> { s -> {
TbTimeSeriesSubscription sub = (TbTimeSeriesSubscription) s; TbTimeSeriesSubscription sub = (TbTimeSeriesSubscription) s;
List<TsKvEntry> updateData = null; List<TsKvEntry> updateData = null;
Map<String, Long> keyStates = sub.getKeyStates();
if (sub.isAllKeys()) { if (sub.isAllKeys()) {
updateData = data; if (sub.isLatestValues()) {
for (TsKvEntry kv : data) {
Long stateTs = keyStates.get(kv.getKey());
if (stateTs == null || kv.getTs() > stateTs) {
if (updateData == null) {
updateData = new ArrayList<>();
}
updateData.add(kv);
}
}
} else {
updateData = data;
}
} else { } else {
for (TsKvEntry kv : data) { for (TsKvEntry kv : data) {
if (sub.getKeyStates().containsKey((kv.getKey()))) { Long stateTs = keyStates.get(kv.getKey());
if (updateData == null) { if (stateTs != null) {
updateData = new ArrayList<>(); if (!sub.isLatestValues() || kv.getTs() > stateTs) {
if (updateData == null) {
updateData = new ArrayList<>();
}
updateData.add(kv);
} }
updateData.add(kv);
} }
} }
} }

2
application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java

@ -31,7 +31,6 @@ import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.sync.ie.EntityExportData; import org.thingsboard.server.common.data.sync.ie.EntityExportData;
import org.thingsboard.server.common.data.sync.ie.EntityImportResult; import org.thingsboard.server.common.data.sync.ie.EntityImportResult;
import org.thingsboard.server.common.data.util.ThrowingRunnable; import org.thingsboard.server.common.data.util.ThrowingRunnable;
import org.thingsboard.server.dao.cf.CalculatedFieldService;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
@ -62,7 +61,6 @@ public class DefaultEntitiesExportImportService implements EntitiesExportImportS
private final Map<EntityType, EntityImportService<?, ?, ?>> importServices = new HashMap<>(); private final Map<EntityType, EntityImportService<?, ?, ?>> importServices = new HashMap<>();
private final RelationService relationService; private final RelationService relationService;
private final CalculatedFieldService calculatedFieldService;
private final RateLimitService rateLimitService; private final RateLimitService rateLimitService;
private final TbLogEntityActionService logEntityActionService; private final TbLogEntityActionService logEntityActionService;

62
application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java

@ -105,6 +105,8 @@ import java.util.function.BiConsumer;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.DataConstants.LATEST_TELEMETRY_SCOPE;
/** /**
* Created by ashvayka on 27.03.18. * Created by ashvayka on 27.03.18.
*/ */
@ -667,24 +669,7 @@ public class DefaultWebSocketService implements WebSocketService {
data.forEach(v -> subState.put(v.getKey(), v.getTs())); data.forEach(v -> subState.put(v.getKey(), v.getTs()));
Lock subLock = new ReentrantLock(); Lock subLock = new ReentrantLock();
TbTimeSeriesSubscription sub = TbTimeSeriesSubscription.builder() TbTimeSeriesSubscription sub = getTsSubscription(subState, subLock, sessionId, sessionRef, cmd, entityId, queryTs, true);
.serviceId(serviceId)
.sessionId(sessionId)
.subscriptionId(registerNewSessionSubId(sessionId, sessionRef, cmd.getCmdId()))
.tenantId(sessionRef.getSecurityCtx().getTenantId())
.entityId(entityId)
.updateProcessor((subscription, update) -> {
subLock.lock();
try {
sendUpdate(subscription.getSessionId(), cmd.getCmdId(), update);
} finally {
subLock.unlock();
}
})
.queryTs(queryTs)
.allKeys(true)
.keyStates(subState)
.build();
subLock.lock(); subLock.lock();
try { try {
@ -712,6 +697,28 @@ public class DefaultWebSocketService implements WebSocketService {
on(r -> Futures.addCallback(tsService.findAllLatest(sessionRef.getSecurityCtx().getTenantId(), entityId), callback, executor), callback::onFailure)); on(r -> Futures.addCallback(tsService.findAllLatest(sessionRef.getSecurityCtx().getTenantId(), entityId), callback, executor), callback::onFailure));
} }
private TbTimeSeriesSubscription getTsSubscription(Map<String, Long> subState, Lock subLock, String sessionId, WebSocketSessionRef sessionRef, TimeseriesSubscriptionCmd cmd, EntityId entityId, long queryTs, boolean allKeys) {
return TbTimeSeriesSubscription.builder()
.serviceId(serviceId)
.sessionId(sessionId)
.subscriptionId(registerNewSessionSubId(sessionId, sessionRef, cmd.getCmdId()))
.tenantId(sessionRef.getSecurityCtx().getTenantId())
.entityId(entityId)
.updateProcessor((subscription, update) -> {
subLock.lock();
try {
sendUpdate(subscription.getSessionId(), cmd.getCmdId(), update);
} finally {
subLock.unlock();
}
})
.queryTs(queryTs)
.allKeys(allKeys)
.keyStates(subState)
.latestValues(LATEST_TELEMETRY_SCOPE.equals(cmd.getScope()))
.build();
}
private FutureCallback<List<TsKvEntry>> getSubscriptionCallback(final WebSocketSessionRef sessionRef, final TimeseriesSubscriptionCmd cmd, private FutureCallback<List<TsKvEntry>> getSubscriptionCallback(final WebSocketSessionRef sessionRef, final TimeseriesSubscriptionCmd cmd,
final String sessionId, final EntityId entityId, final long queryTs, final long startTs, final List<String> keys) { final String sessionId, final EntityId entityId, final long queryTs, final long startTs, final List<String> keys) {
return new FutureCallback<>() { return new FutureCallback<>() {
@ -722,24 +729,7 @@ public class DefaultWebSocketService implements WebSocketService {
data.forEach(v -> subState.put(v.getKey(), v.getTs())); data.forEach(v -> subState.put(v.getKey(), v.getTs()));
Lock subLock = new ReentrantLock(); Lock subLock = new ReentrantLock();
TbTimeSeriesSubscription sub = TbTimeSeriesSubscription.builder() TbTimeSeriesSubscription sub = getTsSubscription(subState, subLock, sessionId, sessionRef, cmd, entityId, queryTs, false);
.serviceId(serviceId)
.sessionId(sessionId)
.subscriptionId(registerNewSessionSubId(sessionId, sessionRef, cmd.getCmdId()))
.tenantId(sessionRef.getSecurityCtx().getTenantId())
.entityId(entityId)
.updateProcessor((subscription, update) -> {
subLock.lock();
try {
sendUpdate(subscription.getSessionId(), cmd.getCmdId(), update);
} finally {
subLock.unlock();
}
})
.queryTs(queryTs)
.allKeys(false)
.keyStates(subState)
.build();
subLock.lock(); subLock.lock();
try { try {

16
application/src/main/resources/banner.txt

@ -1,10 +1,10 @@
______ __ _ ____ __ _____ _ _ ____ _
/_ __/ / /_ (_) ____ ____ _ _____ / __ ) ____ ____ _ _____ ____/ / |_ _| |__ (_)_ __ __ _ ___| __ ) ___ __ _ _ __ __| |
/ / / __ \ / / / __ \ / __ `/ / ___/ / __ | / __ \ / __ `/ / ___/ / __ / | | | '_ \| | '_ \ / _` / __| _ \ / _ \ / _` | '__/ _` |
/ / / / / / / / / / / / / /_/ / (__ ) / /_/ / / /_/ // /_/ / / / / /_/ / | | | | | | | | | | (_| \__ \ |_) | (_) | (_| | | | (_| |
/_/ /_/ /_/ /_/ /_/ /_/ \__, / /____/ /_____/ \____/ \__,_/ /_/ \__,_/ |_| |_| |_|_|_| |_|\__, |___/____/ \___/ \__,_|_| \__,_|
/____/ |___/
=================================================== ===========================================================
:: ${application.title} :: ${application.formatted-version} :: ${application.title} :: ${application.formatted-version}
=================================================== ===========================================================

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

@ -1421,6 +1421,9 @@ device:
port: "${DEVICE_CONNECTIVITY_COAPS_PORT:5684}" port: "${DEVICE_CONNECTIVITY_COAPS_PORT:5684}"
# Path to the COAP CA root certificate file # Path to the COAP CA root certificate file
pem_cert_file: "${DEVICE_CONNECTIVITY_COAPS_CA_ROOT_CERT:cafile.pem}" pem_cert_file: "${DEVICE_CONNECTIVITY_COAPS_CA_ROOT_CERT:cafile.pem}"
gateway:
# The docker tag for thingsboard/tb-gateway image used in docker-compose file for gateway launch
image_version: "${DEVICE_CONNECTIVITY_GATEWAY_IMAGE_VERSION:3.7-stable}"
# Edges parameters # Edges parameters
edges: edges:
@ -1587,11 +1590,24 @@ queue:
# tb_rule_engine.sq: # tb_rule_engine.sq:
# - key: max.poll.records # - key: max.poll.records
# value: "${TB_QUEUE_KAFKA_SQ_MAX_POLL_RECORDS:1024}" # value: "${TB_QUEUE_KAFKA_SQ_MAX_POLL_RECORDS:1024}"
tb_edge:
# Properties for consumers targeting edge service update topics.
- key: max.poll.records
# Define the maximum number of records that can be polled from tb_edge topics per request.
value: "${TB_QUEUE_KAFKA_EDGE_EVENTS_MAX_POLL_RECORDS:10}"
tb_edge.notifications:
# Properties for consumers targeting high-priority edge notifications.
# These notifications include RPC calls, lifecycle events, and new queue messages,
# requiring minimal latency and swift processing.
- key: max.poll.records
# Define the maximum number of records that can be polled from tb_edge.notifications.<SERVICE_ID> topics.
value: "${TB_QUEUE_KAFKA_EDGE_HP_EVENTS_MAX_POLL_RECORDS:10}"
tb_edge_event.notifications: tb_edge_event.notifications:
# Example of specific consumer properties value per topic for edge event # Properties for consumers targeting downlinks meant for specific edge topics.
# Topic names are dynamically constructed using tenant and edge identifiers.
- key: max.poll.records - key: max.poll.records
# Example of specific consumer properties value per topic for edge event # Define the maximum number of records that can be polled from tb_edge_event.notifications.<TENANT_ID>.<EDGE_ID> topics.
value: "${TB_QUEUE_KAFKA_EDGE_EVENT_MAX_POLL_RECORDS:50}" value: "${TB_QUEUE_KAFKA_EDGE_NOTIFICATIONS_MAX_POLL_RECORDS:10}"
tb_housekeeper: tb_housekeeper:
# Consumer properties for Housekeeper tasks topic # Consumer properties for Housekeeper tasks topic
- key: max.poll.records - key: max.poll.records
@ -1762,9 +1778,13 @@ queue:
max_pending_requests: "${TB_EDQS_MAX_PENDING_REQUESTS:10000}" max_pending_requests: "${TB_EDQS_MAX_PENDING_REQUESTS:10000}"
# Maximum timeout for requests to EDQS # Maximum timeout for requests to EDQS
max_request_timeout: "${TB_EDQS_MAX_REQUEST_TIMEOUT:20000}" max_request_timeout: "${TB_EDQS_MAX_REQUEST_TIMEOUT:20000}"
# Strings longer than this threshold will be compressed
string_compression_length_threshold: "${TB_EDQS_STRING_COMPRESSION_LENGTH_THRESHOLD:512}"
stats: stats:
# Enable/disable statistics for EDQS # Enable/disable statistics for EDQS
enabled: "${TB_EDQS_STATS_ENABLED:true}" enabled: "${TB_EDQS_STATS_ENABLED:true}"
# Threshold for slow queries to log, in milliseconds
slow_query_threshold: "${TB_EDQS_SLOW_QUERY_THRESHOLD_MS:3000}"
vc: vc:
# Default topic name # Default topic name
topic: "${TB_QUEUE_VC_TOPIC:tb_version_control}" topic: "${TB_QUEUE_VC_TOPIC:tb_version_control}"
@ -1834,11 +1854,13 @@ queue:
# Interval in milliseconds to poll messages # Interval in milliseconds to poll messages
poll_interval: "${TB_QUEUE_TRANSPORT_NOTIFICATIONS_POLL_INTERVAL_MS:25}" poll_interval: "${TB_QUEUE_TRANSPORT_NOTIFICATIONS_POLL_INTERVAL_MS:25}"
edge: edge:
# Default topic name # Topic name to notify edge service on entity updates, assignment, etc.
topic: "${TB_QUEUE_EDGE_TOPIC:tb_edge}" topic: "${TB_QUEUE_EDGE_TOPIC:tb_edge}"
# For high-priority notifications that require minimum latency and processing time # Topic prefix for high-priority edge notifications (rpc, lifecycle, new messages in queue) that require minimum latency and processing time.
# Each tb-core has its own topic: <PREFIX>.<SERVICE_ID>
notifications_topic: "${TB_QUEUE_EDGE_NOTIFICATIONS_TOPIC:tb_edge.notifications}" notifications_topic: "${TB_QUEUE_EDGE_NOTIFICATIONS_TOPIC:tb_edge.notifications}"
# For edge events messages # Topic prefix for downlinks to be pushed to specific edge.
# Every edge has its own unique topic: <PREFIX>.<TENANT_ID>.<EDGE_ID>
event_notifications_topic: "${TB_QUEUE_EDGE_EVENT_NOTIFICATIONS_TOPIC:tb_edge_event.notifications}" event_notifications_topic: "${TB_QUEUE_EDGE_EVENT_NOTIFICATIONS_TOPIC:tb_edge_event.notifications}"
# Amount of partitions used by Edge services # Amount of partitions used by Edge services
partitions: "${TB_QUEUE_EDGE_PARTITIONS:10}" partitions: "${TB_QUEUE_EDGE_PARTITIONS:10}"

9
application/src/test/java/org/thingsboard/server/controller/CalculatedFieldControllerTest.java

@ -31,7 +31,6 @@ import org.thingsboard.server.common.data.cf.configuration.OutputType;
import org.thingsboard.server.common.data.cf.configuration.ReferencedEntityKey; import org.thingsboard.server.common.data.cf.configuration.ReferencedEntityKey;
import org.thingsboard.server.common.data.cf.configuration.SimpleCalculatedFieldConfiguration; import org.thingsboard.server.common.data.cf.configuration.SimpleCalculatedFieldConfiguration;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.service.DaoSqlTest;
@ -85,7 +84,7 @@ public class CalculatedFieldControllerTest extends AbstractControllerTest {
assertThat(savedCalculatedField.getEntityId()).isEqualTo(calculatedField.getEntityId()); assertThat(savedCalculatedField.getEntityId()).isEqualTo(calculatedField.getEntityId());
assertThat(savedCalculatedField.getType()).isEqualTo(calculatedField.getType()); assertThat(savedCalculatedField.getType()).isEqualTo(calculatedField.getType());
assertThat(savedCalculatedField.getName()).isEqualTo(calculatedField.getName()); assertThat(savedCalculatedField.getName()).isEqualTo(calculatedField.getName());
assertThat(savedCalculatedField.getConfiguration()).isEqualTo(getCalculatedFieldConfig(testDevice.getId())); assertThat(savedCalculatedField.getConfiguration()).isEqualTo(getCalculatedFieldConfig());
assertThat(savedCalculatedField.getVersion()).isEqualTo(1L); assertThat(savedCalculatedField.getVersion()).isEqualTo(1L);
savedCalculatedField.setName("Test CF"); savedCalculatedField.setName("Test CF");
@ -134,16 +133,16 @@ public class CalculatedFieldControllerTest extends AbstractControllerTest {
calculatedField.setType(CalculatedFieldType.SIMPLE); calculatedField.setType(CalculatedFieldType.SIMPLE);
calculatedField.setName("Test Calculated Field"); calculatedField.setName("Test Calculated Field");
calculatedField.setConfigurationVersion(1); calculatedField.setConfigurationVersion(1);
calculatedField.setConfiguration(getCalculatedFieldConfig(null)); calculatedField.setConfiguration(getCalculatedFieldConfig());
calculatedField.setVersion(1L); calculatedField.setVersion(1L);
return calculatedField; return calculatedField;
} }
private CalculatedFieldConfiguration getCalculatedFieldConfig(EntityId referencedEntityId) { private CalculatedFieldConfiguration getCalculatedFieldConfig() {
SimpleCalculatedFieldConfiguration config = new SimpleCalculatedFieldConfiguration(); SimpleCalculatedFieldConfiguration config = new SimpleCalculatedFieldConfiguration();
Argument argument = new Argument(); Argument argument = new Argument();
argument.setRefEntityId(referencedEntityId); argument.setRefEntityId(null);
ReferencedEntityKey refEntityKey = new ReferencedEntityKey("temperature", ArgumentType.TS_LATEST, null); ReferencedEntityKey refEntityKey = new ReferencedEntityKey("temperature", ArgumentType.TS_LATEST, null);
argument.setRefEntityKey(refEntityKey); argument.setRefEntityKey(refEntityKey);

8
application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java

@ -23,6 +23,7 @@ import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.TestPropertySource;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
@ -94,6 +95,9 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest {
private DeviceProfileId mqttDeviceProfileId; private DeviceProfileId mqttDeviceProfileId;
private DeviceProfileId coapDeviceProfileId; private DeviceProfileId coapDeviceProfileId;
@Value("${device.connectivity.gateway.image_version:3.7-stable}")
private String gatewayImageVersion;
@Before @Before
public void beforeTest() throws Exception { public void beforeTest() throws Exception {
loginSysAdmin(); loginSysAdmin();
@ -298,7 +302,7 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest {
"services:\n" + "services:\n" +
" # ThingsBoard IoT Gateway Service Configuration\n" + " # ThingsBoard IoT Gateway Service Configuration\n" +
" tb-gateway:\n" + " tb-gateway:\n" +
" image: thingsboard/tb-gateway\n" + " image: thingsboard/tb-gateway:" + gatewayImageVersion + "\n" +
" container_name: tb-gateway\n" + " container_name: tb-gateway\n" +
" restart: always\n" + " restart: always\n" +
"\n" + "\n" +
@ -847,7 +851,7 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest {
"-t \"application/json\" -e \"{temperature:25}\" coap://test.domain:5683/api/v1/%s/telemetry", credentials.getCredentialsId())); "-t \"application/json\" -e \"{temperature:25}\" coap://test.domain:5683/api/v1/%s/telemetry", credentials.getCredentialsId()));
assertThat(linuxCoapCommands.get(COAPS).get(0).asText()).isEqualTo("curl -f -S -o " + CA_ROOT_CERT_PEM + " http://localhost:80/api/device-connectivity/coaps/certificate/download"); assertThat(linuxCoapCommands.get(COAPS).get(0).asText()).isEqualTo("curl -f -S -o " + CA_ROOT_CERT_PEM + " http://localhost:80/api/device-connectivity/coaps/certificate/download");
assertThat(linuxCoapCommands.get(COAPS).get(1).asText()).isEqualTo(String.format("coap-client-openssl -v 6 -m POST " + assertThat(linuxCoapCommands.get(COAPS).get(1).asText()).isEqualTo(String.format("coap-client-openssl -v 6 -m POST " +
"-R "+ CA_ROOT_CERT_PEM + " -t \"application/json\" -e \"{temperature:25}\" coaps://test.domain:5684/api/v1/%s/telemetry", credentials.getCredentialsId())); "-R " + CA_ROOT_CERT_PEM + " -t \"application/json\" -e \"{temperature:25}\" coaps://test.domain:5684/api/v1/%s/telemetry", credentials.getCredentialsId()));
JsonNode dockerCoapCommands = commands.get(COAP).get(DOCKER); JsonNode dockerCoapCommands = commands.get(COAP).get(DOCKER);
assertThat(dockerCoapCommands.get(COAP).asText()).isEqualTo(String.format("docker run --rm -it " + assertThat(dockerCoapCommands.get(COAP).asText()).isEqualTo(String.format("docker run --rm -it " +

4
application/src/test/java/org/thingsboard/server/controller/EdgeControllerTest.java

@ -885,6 +885,10 @@ public class EdgeControllerTest extends AbstractControllerTest {
device.setType("default"); device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class); Device savedDevice = doPost("/api/device", device, Device.class);
// create public customer
doPost("/api/customer/public/device/" + savedDevice.getId().getId(), Device.class);
doDelete("/api/customer/device/" + savedDevice.getId().getId(), Device.class);
simulateEdgeActivation(edge); simulateEdgeActivation(edge);
doPost("/api/edge/" + edge.getId().getId().toString() doPost("/api/edge/" + edge.getId().getId().toString()

11
application/src/test/java/org/thingsboard/server/controller/EntityQueryControllerTest.java

@ -499,7 +499,8 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
); );
EntityDataPageLink pageLink = new EntityDataPageLink(10, 0, null, sortOrder); EntityDataPageLink pageLink = new EntityDataPageLink(10, 0, null, sortOrder);
List<EntityKey> entityFields = Collections.singletonList(new EntityKey(EntityKeyType.ENTITY_FIELD, "name")); List<EntityKey> entityFields = Collections.singletonList(new EntityKey(EntityKeyType.ENTITY_FIELD, "name"));
List<EntityKey> latestValues = Collections.singletonList(new EntityKey(EntityKeyType.ATTRIBUTE, "temperature")); List<EntityKey> latestValues = List.of(new EntityKey(EntityKeyType.ATTRIBUTE, "temperature"),
new EntityKey(EntityKeyType.ATTRIBUTE, "non-existing-attribute"));
EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, null); EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, null);
PageData<EntityData> data = findByQueryAndCheck(query, 67); PageData<EntityData> data = findByQueryAndCheck(query, 67);
@ -517,6 +518,14 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
List<String> deviceTemperatures = temperatures.stream().map(aLong -> Long.toString(aLong)).collect(Collectors.toList()); List<String> deviceTemperatures = temperatures.stream().map(aLong -> Long.toString(aLong)).collect(Collectors.toList());
Assert.assertEquals(deviceTemperatures, loadedTemperatures); Assert.assertEquals(deviceTemperatures, loadedTemperatures);
// check ts value == 0, value is empty string for non-existing data points
List<TsValue> loadedNonExistingAttributes = loadedEntities.stream().map(entityData ->
entityData.getLatest().get(EntityKeyType.ATTRIBUTE).get("non-existing-attribute")).toList();
loadedNonExistingAttributes.forEach(tsValue -> {
assertThat(tsValue.getTs()).isEqualTo(0L);
assertThat(tsValue.getValue()).isEqualTo("");
});
pageLink = new EntityDataPageLink(10, 0, null, sortOrder); pageLink = new EntityDataPageLink(10, 0, null, sortOrder);
KeyFilter highTemperatureFilter = new KeyFilter(); KeyFilter highTemperatureFilter = new KeyFilter();
highTemperatureFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "temperature")); highTemperatureFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "temperature"));

14
application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java

@ -31,6 +31,7 @@ import org.thingsboard.server.service.ws.AuthCmd;
import org.thingsboard.server.service.ws.WsCmd; import org.thingsboard.server.service.ws.WsCmd;
import org.thingsboard.server.service.ws.WsCommandsWrapper; import org.thingsboard.server.service.ws.WsCommandsWrapper;
import org.thingsboard.server.service.ws.telemetry.cmd.v1.AttributesSubscriptionCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v1.AttributesSubscriptionCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v1.TimeseriesSubscriptionCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmCountUpdate; import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmCountUpdate;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountUpdate; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountUpdate;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataCmd;
@ -38,6 +39,7 @@ import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataUpdate;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityHistoryCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityHistoryCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.LatestValueCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.LatestValueCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.TimeSeriesCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.TimeSeriesCmd;
import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate;
import java.net.URI; import java.net.URI;
import java.nio.channels.NotYetConnectedException; import java.nio.channels.NotYetConnectedException;
@ -271,6 +273,18 @@ public class TbTestWebSocketClient extends WebSocketClient {
return sendEntityDataQuery(edq); return sendEntityDataQuery(edq);
} }
public JsonNode sendTimeseriesCmd(EntityId entityId, String scope) {
log.warn("sendTimeseriesCmd entityId: {}, scope: {}", entityId, scope);
TimeseriesSubscriptionCmd cmd = new TimeseriesSubscriptionCmd(0, 0, 0, 10, null);
cmd.setEntityId(entityId.getId().toString());
cmd.setEntityType(entityId.getEntityType().toString());
cmd.setCmdId(1);
cmd.setScope(scope);
send(cmd);
String msg = this.waitForReply();
return JacksonUtil.fromString(msg, JsonNode.class);
}
public void send(WsCmd... cmds) { public void send(WsCmd... cmds) {
WsCommandsWrapper cmdsWrapper = new WsCommandsWrapper(); WsCommandsWrapper cmdsWrapper = new WsCommandsWrapper();
cmdsWrapper.setCmds(List.of(cmds)); cmdsWrapper.setCmds(List.of(cmds));

11
application/src/test/java/org/thingsboard/server/controller/UserControllerTest.java

@ -233,16 +233,9 @@ public class UserControllerTest extends AbstractControllerTest {
.put("password", "testPassword2"); .put("password", "testPassword2");
Mockito.doNothing().when(mailService).sendPasswordWasResetEmail(anyString(), anyString()); Mockito.doNothing().when(mailService).sendPasswordWasResetEmail(anyString(), anyString());
JsonNode tokenInfo = readResponse( doPost("/api/noauth/resetPassword", resetPasswordRequest)
doPost("/api/noauth/resetPassword", resetPasswordRequest) .andExpect(status().isOk());
.andExpect(status().isOk()), JsonNode.class);
Mockito.verify(mailService).sendPasswordWasResetEmail(anyString(), anyString()); Mockito.verify(mailService).sendPasswordWasResetEmail(anyString(), anyString());
validateAndSetJwtToken(tokenInfo, email);
doGet("/api/auth/user")
.andExpect(status().isOk())
.andExpect(jsonPath("$.authority", is(Authority.TENANT_ADMIN.name())))
.andExpect(jsonPath("$.email", is(email)));
resetTokens(); resetTokens();

29
application/src/test/java/org/thingsboard/server/controller/WebsocketApiTest.java

@ -68,11 +68,13 @@ import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmStatusUpdate;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountUpdate; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountUpdate;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataUpdate; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataUpdate;
import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -584,6 +586,33 @@ public class WebsocketApiTest extends AbstractControllerTest {
Assert.assertNull(msg); Assert.assertNull(msg);
} }
@Test
public void testTimeseriesSubscriptionCmd() throws Exception {
long now = System.currentTimeMillis() - 100;
long lastTs = now - TimeUnit.MINUTES.toMillis(1);
TsKvEntry dataPoint1 = new BasicTsKvEntry(lastTs, new LongDataEntry("temperature", 42L));
sendTelemetry(device, List.of(dataPoint1));
JsonNode update = getWsClient().sendTimeseriesCmd(device.getId(), "LATEST_TELEMETRY");
JsonNode data = update.get("data");
Assert.assertEquals(1, data.size());
Assert.assertEquals(JacksonUtil.newArrayNode().add(lastTs).add("42"), data.get("temperature").get(0));
//Sending update from the past, while latest value has new timestamp;
TsKvEntry dataPoint4 = new BasicTsKvEntry(now - TimeUnit.MINUTES.toMillis(5), new LongDataEntry("temperature", 45L));
getWsClient().registerWaitForUpdate();
sendTelemetry(device, List.of(dataPoint4));
String msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1));
Assert.assertNull(msg);
//Sending duplicate update again
getWsClient().registerWaitForUpdate();
sendTelemetry(device, List.of(dataPoint4));
msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1));
Assert.assertNull(msg);
}
@Test @Test
public void testEntityDataLatestTsWsCmd() throws Exception { public void testEntityDataLatestTsWsCmd() throws Exception {
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();

4
application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java

@ -168,6 +168,10 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest {
Device savedDevice = saveDevice("Edge Device 1", THERMOSTAT_DEVICE_PROFILE_NAME); Device savedDevice = saveDevice("Edge Device 1", THERMOSTAT_DEVICE_PROFILE_NAME);
// create public customer
doPost("/api/customer/public/device/" + savedDevice.getId().getId(), Device.class);
doDelete("/api/customer/device/" + savedDevice.getId().getId(), Device.class);
Asset savedAsset = saveAsset("Edge Asset 1"); Asset savedAsset = saveAsset("Edge Asset 1");
updateRootRuleChainMetadata(); updateRootRuleChainMetadata();

14
application/src/test/java/org/thingsboard/server/edge/TenantProfileEdgeTest.java

@ -44,9 +44,8 @@ public class TenantProfileEdgeTest extends AbstractEdgeTest {
@Test @Test
public void testTenantProfiles() throws Exception { public void testTenantProfiles() throws Exception {
loginSysAdmin(); loginSysAdmin();
TenantProfile originalTenantProfile = doGet("/api/tenantProfile/" + tenantProfileId.getId(), TenantProfile.class);
// save current values into tmp to revert after test TenantProfile edgeTenantProfile = new TenantProfile(originalTenantProfile);
TenantProfile edgeTenantProfile = doGet("/api/tenantProfile/" + tenantProfileId.getId(), TenantProfile.class);
// updated edge tenant profile // updated edge tenant profile
edgeTenantProfile.setName("Tenant Profile Edge Test"); edgeTenantProfile.setName("Tenant Profile Edge Test");
@ -64,14 +63,15 @@ public class TenantProfileEdgeTest extends AbstractEdgeTest {
Assert.assertEquals("Updated tenant profile Edge Test", tenantProfileMsg.getDescription()); Assert.assertEquals("Updated tenant profile Edge Test", tenantProfileMsg.getDescription());
Assert.assertEquals("Tenant Profile Edge Test", tenantProfileMsg.getName()); Assert.assertEquals("Tenant Profile Edge Test", tenantProfileMsg.getName());
doPost("/api/tenantProfile", originalTenantProfile, TenantProfile.class);
loginTenantAdmin(); loginTenantAdmin();
} }
@Test @Test
public void testIsolatedTenantProfile() throws Exception { public void testIsolatedTenantProfile() throws Exception {
loginSysAdmin(); loginSysAdmin();
TenantProfile originalTenantProfile = doGet("/api/tenantProfile/" + tenantProfileId.getId(), TenantProfile.class);
TenantProfile edgeTenantProfile = doGet("/api/tenantProfile/" + tenantProfileId.getId(), TenantProfile.class); TenantProfile edgeTenantProfile = new TenantProfile(originalTenantProfile);
// set tenant profile isolated and add 2 queues - main and isolated // set tenant profile isolated and add 2 queues - main and isolated
edgeTenantProfile.setIsolatedTbRuleEngine(true); edgeTenantProfile.setIsolatedTbRuleEngine(true);
@ -110,6 +110,10 @@ public class TenantProfileEdgeTest extends AbstractEdgeTest {
Assert.assertNotNull(queue); Assert.assertNotNull(queue);
Assert.assertEquals(tenantId, queue.getTenantId()); Assert.assertEquals(tenantId, queue.getTenantId());
} }
loginSysAdmin();
doPost("/api/tenantProfile", originalTenantProfile, TenantProfile.class);
loginTenantAdmin();
} }
private TenantProfileQueueConfiguration createQueueConfig(String queueName, String queueTopic) { private TenantProfileQueueConfiguration createQueueConfig(String queueName, String queueTopic) {

156
application/src/test/java/org/thingsboard/server/service/edge/EdgeMsgConstructorUtilsTest.java

@ -0,0 +1,156 @@
/**
* Copyright © 2016-2025 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.edge;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.action.TbSaveToCustomCassandraTableNode;
import org.thingsboard.rule.engine.api.NodeConfiguration;
import org.thingsboard.rule.engine.api.TbNode;
import org.thingsboard.rule.engine.aws.lambda.TbAwsLambdaNode;
import org.thingsboard.rule.engine.filter.TbCheckRelationNode;
import org.thingsboard.rule.engine.flow.TbAckNode;
import org.thingsboard.rule.engine.math.TbMathNode;
import org.thingsboard.rule.engine.metadata.CalculateDeltaNode;
import org.thingsboard.rule.engine.metadata.TbGetTelemetryNode;
import org.thingsboard.rule.engine.rest.TbSendRestApiCallReplyNode;
import org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode;
import org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import java.lang.reflect.Constructor;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
import static org.thingsboard.server.service.edge.EdgeMsgConstructorUtils.EXCLUDED_NODES_BY_EDGE_VERSION;
import static org.thingsboard.server.service.edge.EdgeMsgConstructorUtils.IGNORED_PARAMS_BY_EDGE_VERSION;
@Slf4j
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class EdgeMsgConstructorUtilsTest {
private static final int CONFIGURATION_VERSION = 5;
static Stream<EdgeVersion> provideEdgeVersions() {
return Stream.of(
EdgeVersion.V_4_0_0,
EdgeVersion.V_3_9_0,
EdgeVersion.V_3_8_0,
EdgeVersion.V_3_7_0
);
}
private static final RuleChainMetaData RULE_CHAIN_META_DATA = new RuleChainMetaData();
private static final List<TbNode> TEST_NODES =
List.of(
new TbSaveToCustomCassandraTableNode(),
new TbMsgAttributesNode(),
new TbMsgTimeseriesNode(),
new TbSendRestApiCallReplyNode(),
new TbAwsLambdaNode(),
new TbMathNode(),
new CalculateDeltaNode(),
new TbAckNode(),
new TbCheckRelationNode(),
new TbGetTelemetryNode()
);
@BeforeAll
static void setUp() {
List<RuleNode> ruleNodes = TEST_NODES.stream()
.map(node -> {
RuleNode ruleNode = new RuleNode();
ruleNode.setName(node.getClass().getName());
ruleNode.setType(node.getClass().getName());
ruleNode.setConfigurationVersion(CONFIGURATION_VERSION);
ruleNode.setConfiguration(JacksonUtil.valueToTree(createDefaultConfiguration(node)));
return ruleNode;
})
.toList();
RULE_CHAIN_META_DATA.setFirstNodeIndex(0);
RULE_CHAIN_META_DATA.setNodes(ruleNodes);
}
private static NodeConfiguration<?> createDefaultConfiguration(TbNode node) {
try {
org.thingsboard.rule.engine.api.RuleNode annotation = node.getClass().getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class);
Constructor<?> constructor = annotation.configClazz().getConstructor();
NodeConfiguration<?> configInstance = (NodeConfiguration<?>) constructor.newInstance();
return configInstance.defaultConfiguration();
} catch (Exception e) {
throw new RuntimeException("Exception during creating RuleNodeConfiguration for node - " + node, e);
}
}
@ParameterizedTest(name = "Test Sanitize Metadata For Edge: {0}")
@MethodSource("provideEdgeVersions")
@DisplayName("Test Sanitize Metadata For Legacy Edge Version")
public void testSanitizeMetadataForLegacyEdgeVersion(EdgeVersion edgeVersion) {
// WHEN
List<RuleNode> ruleNodes = sanitizeMetadataForLegacyEdgeVersion(edgeVersion);
// THEN
ruleNodes.forEach(ruleNode -> {
checkUpdateNodeConfigurationsForLegacyEdge(ruleNode, edgeVersion);
checkRemoveExcludedNodesForLegacyEdge(ruleNode, edgeVersion);
});
}
private List<RuleNode> sanitizeMetadataForLegacyEdgeVersion(EdgeVersion edgeVersion) {
String metadataUpdateMsg = EdgeMsgConstructorUtils.constructRuleChainMetadataUpdatedMsg(
UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE,
RULE_CHAIN_META_DATA,
edgeVersion
).getEntity();
RuleChainMetaData updatedMetaData = JacksonUtil.fromString(metadataUpdateMsg, RuleChainMetaData.class, true);
Assertions.assertNotNull(updatedMetaData, "RuleChainMetaData should not be null after update.");
return updatedMetaData.getNodes();
}
private void checkUpdateNodeConfigurationsForLegacyEdge(RuleNode ruleNode, EdgeVersion edgeVersion) {
if (IGNORED_PARAMS_BY_EDGE_VERSION.containsKey(edgeVersion) && IGNORED_PARAMS_BY_EDGE_VERSION.get(edgeVersion).containsKey(ruleNode.getType())) {
String ignoredParam = IGNORED_PARAMS_BY_EDGE_VERSION.get(edgeVersion).get(ruleNode.getType());
Assertions.assertFalse(ruleNode.getConfiguration().has(ignoredParam),
String.format("RuleNode '%s' for EdgeVersion '%s' should ignore '%s' config parameter.", ruleNode.getName(), edgeVersion, ignoredParam));
}
}
private void checkRemoveExcludedNodesForLegacyEdge(RuleNode ruleNode, EdgeVersion edgeVersion) {
boolean isNodeExcluded = Optional.ofNullable(EXCLUDED_NODES_BY_EDGE_VERSION.get(edgeVersion))
.map(excludedNodes -> !excludedNodes.contains(ruleNode.getType()))
.orElse(true);
Assertions.assertTrue(isNodeExcluded,
String.format("For EdgeVersion '%s', ruleNode '%s' should not be included.", edgeVersion, ruleNode.getType()));
}
}

21
application/src/test/java/org/thingsboard/server/service/entitiy/EntityServiceTest.java

@ -95,7 +95,6 @@ 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.EntityViewDao; import org.thingsboard.server.dao.entityview.EntityViewDao;
import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity;
import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.dao.sql.relation.RelationRepository; import org.thingsboard.server.dao.sql.relation.RelationRepository;
@ -1664,7 +1663,7 @@ public class EntityServiceTest extends AbstractControllerTest {
List<ListenableFuture<TimeseriesSaveResult>> timeseriesFutures = new ArrayList<>(); List<ListenableFuture<TimeseriesSaveResult>> timeseriesFutures = new ArrayList<>();
for (int i = 0; i < devices.size(); i++) { for (int i = 0; i < devices.size(); i++) {
Device device = devices.get(i); Device device = devices.get(i);
timeseriesFutures.add(saveLongTimeseries(device.getId(), "temperature", temperatures.get(i))); timeseriesFutures.add(saveTimeseries(device.getId(), "temperature", temperatures.get(i)));
} }
Futures.allAsList(timeseriesFutures).get(); Futures.allAsList(timeseriesFutures).get();
@ -1711,6 +1710,13 @@ public class EntityServiceTest extends AbstractControllerTest {
List<String> entitiesTelemetry = loadedEntities.stream().map(entityData -> entityData.getLatest().get(EntityKeyType.TIME_SERIES).get("temperature").getValue()).toList(); List<String> entitiesTelemetry = loadedEntities.stream().map(entityData -> entityData.getLatest().get(EntityKeyType.TIME_SERIES).get("temperature").getValue()).toList();
assertThat(entitiesTelemetry).containsExactlyElementsOf(expectedSortedList); assertThat(entitiesTelemetry).containsExactlyElementsOf(expectedSortedList);
// update temperature to long value for one of device
long longTempValue = -100L;
saveTimeseries(devices.get(new Random().nextInt(66)).getId(), "temperature", longTempValue).get();
loadedEntities = loadAllData(querySortByTemp, deviceTemperatures.size());
entitiesTelemetry = loadedEntities.stream().map(entityData -> entityData.getLatest().get(EntityKeyType.TIME_SERIES).get("temperature").getValue()).toList();
assertThat(entitiesTelemetry.get(0)).isEqualTo(String.valueOf(longTempValue));
deviceService.deleteDevicesByTenantId(tenantId); deviceService.deleteDevicesByTenantId(tenantId);
} }
@ -2252,15 +2258,18 @@ public class EntityServiceTest extends AbstractControllerTest {
return attributesService.save(tenantId, entityId, scope, Collections.singletonList(attr)); return attributesService.save(tenantId, entityId, scope, Collections.singletonList(attr));
} }
private ListenableFuture<TimeseriesSaveResult> saveLongTimeseries(EntityId entityId, String key, Double value) { private ListenableFuture<TimeseriesSaveResult> saveTimeseries(EntityId entityId, String key, Double value) {
TsKvEntity tsKv = new TsKvEntity();
tsKv.setStrKey(key);
tsKv.setDoubleValue(value);
KvEntry telemetryValue = new DoubleDataEntry(key, value); KvEntry telemetryValue = new DoubleDataEntry(key, value);
BasicTsKvEntry timeseries = new BasicTsKvEntry(42L, telemetryValue); BasicTsKvEntry timeseries = new BasicTsKvEntry(42L, telemetryValue);
return timeseriesService.save(tenantId, entityId, timeseries); return timeseriesService.save(tenantId, entityId, timeseries);
} }
private ListenableFuture<TimeseriesSaveResult> saveTimeseries(EntityId entityId, String key, Long value) {
KvEntry telemetryValue = new LongDataEntry(key, value);
BasicTsKvEntry timeseries = new BasicTsKvEntry(42L, telemetryValue);
return timeseriesService.save(tenantId, entityId, timeseries);
}
protected void createMultiRootHierarchy(List<Asset> buildings, List<Asset> apartments, protected void createMultiRootHierarchy(List<Asset> buildings, List<Asset> apartments,
Map<String, Map<UUID, String>> entityNameByTypeMap, Map<String, Map<UUID, String>> entityNameByTypeMap,
Map<UUID, UUID> childParentRelationMap) throws InterruptedException { Map<UUID, UUID> childParentRelationMap) throws InterruptedException {

2
application/src/test/java/org/thingsboard/server/service/sync/vc/VersionControlTest.java

@ -621,6 +621,7 @@ public class VersionControlTest extends AbstractControllerTest {
assertThat(importedField.getName()).isEqualTo(deviceCalculatedField.getName()); assertThat(importedField.getName()).isEqualTo(deviceCalculatedField.getName());
assertThat(importedField.getType()).isEqualTo(deviceCalculatedField.getType()); assertThat(importedField.getType()).isEqualTo(deviceCalculatedField.getType());
assertThat(importedField.getId()).isNotEqualTo(deviceCalculatedField.getId()); assertThat(importedField.getId()).isNotEqualTo(deviceCalculatedField.getId());
assertThat(importedField.getConfiguration().getArguments().get("T").getRefEntityId()).isEqualTo(importedAsset.getId());
}); });
List<CalculatedField> importedAssetCalculatedFields = findCalculatedFieldsByEntityId(importedAsset.getId()); List<CalculatedField> importedAssetCalculatedFields = findCalculatedFieldsByEntityId(importedAsset.getId());
@ -629,6 +630,7 @@ public class VersionControlTest extends AbstractControllerTest {
assertThat(importedField.getName()).isEqualTo(assetCalculatedField.getName()); assertThat(importedField.getName()).isEqualTo(assetCalculatedField.getName());
assertThat(importedField.getType()).isEqualTo(assetCalculatedField.getType()); assertThat(importedField.getType()).isEqualTo(assetCalculatedField.getType());
assertThat(importedField.getId()).isNotEqualTo(assetCalculatedField.getId()); assertThat(importedField.getId()).isNotEqualTo(assetCalculatedField.getId());
assertThat(importedField.getConfiguration().getArguments().get("T").getRefEntityId()).isEqualTo(importedDevice.getId());
}); });
} }

2
common/dao-api/src/main/java/org/thingsboard/server/dao/customer/CustomerService.java

@ -41,6 +41,8 @@ public interface CustomerService extends EntityDaoService {
Customer findOrCreatePublicCustomer(TenantId tenantId); Customer findOrCreatePublicCustomer(TenantId tenantId);
Customer findPublicCustomer(TenantId tenantId);
PageData<Customer> findCustomersByTenantId(TenantId tenantId, PageLink pageLink); PageData<Customer> findCustomersByTenantId(TenantId tenantId, PageLink pageLink);
void deleteCustomersByTenantId(TenantId tenantId); void deleteCustomersByTenantId(TenantId tenantId);

2
common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java

@ -148,4 +148,6 @@ public class DataConstants {
public static final String CF_QUEUE_NAME = "CalculatedFields"; public static final String CF_QUEUE_NAME = "CalculatedFields";
public static final String CF_STATES_QUEUE_NAME = "CalculatedFieldStates"; public static final String CF_STATES_QUEUE_NAME = "CalculatedFieldStates";
public static final String LATEST_TELEMETRY_SCOPE = "LATEST_TELEMETRY";
} }

4
common/data/src/main/java/org/thingsboard/server/common/data/JavaSerDesUtil.java

@ -36,7 +36,7 @@ public class JavaSerDesUtil {
try (ObjectInputStream ois = new ObjectInputStream(is)) { try (ObjectInputStream ois = new ObjectInputStream(is)) {
return (T) ois.readObject(); return (T) ois.readObject();
} catch (IOException | ClassNotFoundException e) { } catch (IOException | ClassNotFoundException e) {
log.error("Error during deserialization message, [{}]", e.getMessage()); log.error("Error during deserialization", e);
return null; return null;
} }
} }
@ -50,7 +50,7 @@ public class JavaSerDesUtil {
ois.writeObject(msq); ois.writeObject(msq);
return boas.toByteArray(); return boas.toByteArray();
} catch (IOException e) { } catch (IOException e) {
log.error("Error during serialization message, [{}]", e.getMessage()); log.error("Error during serialization", e);
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }

26
common/data/src/main/java/org/thingsboard/server/common/data/cf/CalculatedField.java

@ -64,7 +64,7 @@ public class CalculatedField extends BaseData<CalculatedFieldId> implements HasN
@Schema(description = "Version of calculated field configuration.", example = "0") @Schema(description = "Version of calculated field configuration.", example = "0")
private int configurationVersion; private int configurationVersion;
@Schema(implementation = SimpleCalculatedFieldConfiguration.class) @Schema(implementation = SimpleCalculatedFieldConfiguration.class)
private transient CalculatedFieldConfiguration configuration; private CalculatedFieldConfiguration configuration;
@Getter @Getter
@Setter @Setter
private Long version; private Long version;
@ -112,6 +112,18 @@ public class CalculatedField extends BaseData<CalculatedFieldId> implements HasN
return super.getCreatedTime(); return super.getCreatedTime();
} }
// Getter is ignored for serialization
@JsonIgnore
public boolean isDebugMode() {
return debugMode;
}
// Setter is annotated for deserialization
@JsonSetter
public void setDebugMode(boolean debugMode) {
this.debugMode = debugMode;
}
@Override @Override
public String toString() { public String toString() {
return new StringBuilder() return new StringBuilder()
@ -128,16 +140,4 @@ public class CalculatedField extends BaseData<CalculatedFieldId> implements HasN
.toString(); .toString();
} }
// Getter is ignored for serialization
@JsonIgnore
public boolean isDebugMode() {
return debugMode;
}
// Setter is annotated for deserialization
@JsonSetter
public void setDebugMode(boolean debugMode) {
this.debugMode = debugMode;
}
} }

3
common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/ScriptCalculatedFieldConfiguration.java

@ -16,13 +16,16 @@
package org.thingsboard.server.common.data.cf.configuration; package org.thingsboard.server.common.data.cf.configuration;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.cf.CalculatedFieldType; import org.thingsboard.server.common.data.cf.CalculatedFieldType;
@Data @Data
@EqualsAndHashCode(callSuper = true)
public class ScriptCalculatedFieldConfiguration extends BaseCalculatedFieldConfiguration implements CalculatedFieldConfiguration { public class ScriptCalculatedFieldConfiguration extends BaseCalculatedFieldConfiguration implements CalculatedFieldConfiguration {
@Override @Override
public CalculatedFieldType getType() { public CalculatedFieldType getType() {
return CalculatedFieldType.SCRIPT; return CalculatedFieldType.SCRIPT;
} }
} }

3
common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/SimpleCalculatedFieldConfiguration.java

@ -16,13 +16,16 @@
package org.thingsboard.server.common.data.cf.configuration; package org.thingsboard.server.common.data.cf.configuration;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.cf.CalculatedFieldType; import org.thingsboard.server.common.data.cf.CalculatedFieldType;
@Data @Data
@EqualsAndHashCode(callSuper = true)
public class SimpleCalculatedFieldConfiguration extends BaseCalculatedFieldConfiguration implements CalculatedFieldConfiguration { public class SimpleCalculatedFieldConfiguration extends BaseCalculatedFieldConfiguration implements CalculatedFieldConfiguration {
@Override @Override
public CalculatedFieldType getType() { public CalculatedFieldType getType() {
return CalculatedFieldType.SIMPLE; return CalculatedFieldType.SIMPLE;
} }
} }

2
common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/FieldsUtil.java

@ -289,7 +289,7 @@ public class FieldsUtil {
} }
public static String getText(JsonNode node) { public static String getText(JsonNode node) {
return node != null ? node.toString() : ""; return node != null && !node.isNull() ? node.toString() : "";
} }
private static UUID getCustomerId(CustomerId customerId) { private static UUID getCustomerId(CustomerId customerId) {

5
common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/UserFields.java

@ -35,6 +35,11 @@ public class UserFields extends AbstractEntityFields {
private String phone; private String phone;
private String additionalInfo; private String additionalInfo;
@Override
public String getName() {
return super.getEmail();
}
public UserFields(UUID id, long createdTime, UUID tenantId, UUID customerId, public UserFields(UUID id, long createdTime, UUID tenantId, UUID customerId,
Long version, String firstName, String lastName, String email, Long version, String firstName, String lastName, String email,
String phone, JsonNode additionalInfo) { String phone, JsonNode additionalInfo) {

3
common/data/src/main/java/org/thingsboard/server/common/data/event/CalculatedFieldDebugEventFilter.java

@ -37,10 +37,9 @@ public class CalculatedFieldDebugEventFilter extends DebugEventFilter {
example = "{\"x\":{\"ts\":1739432016629,\"value\":20},\"y\":{\"ts\":1739429717656,\"value\":12}}") example = "{\"x\":{\"ts\":1739432016629,\"value\":20},\"y\":{\"ts\":1739429717656,\"value\":12}}")
protected String arguments; protected String arguments;
@Schema(description = "String value representing the result of a calculation", @Schema(description = "String value representing the result of a calculation",
example = "{\"x + y\":54}") example = "{\"x + y\":32}")
protected String result; protected String result;
@Override @Override
public EventType getEventType() { public EventType getEventType() {
return EventType.DEBUG_CALCULATED_FIELD; return EventType.DEBUG_CALCULATED_FIELD;

2
common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityExportData.java

@ -107,7 +107,7 @@ public class EntityExportData<E extends ExportableEntity<? extends EntityId>> {
@JsonIgnore @JsonIgnore
public boolean hasCalculatedFields() { public boolean hasCalculatedFields() {
return calculatedFields != null; return calculatedFields != null && !calculatedFields.isEmpty();
} }
} }

6
common/edqs/src/main/java/org/thingsboard/server/edqs/data/dp/BoolDataPoint.java

@ -46,6 +46,10 @@ public class BoolDataPoint extends AbstractDataPoint {
@Override @Override
public int compareTo(DataPoint dataPoint) { public int compareTo(DataPoint dataPoint) {
return Boolean.compare(value, dataPoint.getBool()); if (dataPoint.getType() == DataType.BOOLEAN) {
return Boolean.compare(value, dataPoint.getBool());
} else {
return super.compareTo(dataPoint);
}
} }
} }

3
common/edqs/src/main/java/org/thingsboard/server/edqs/data/dp/CompressedStringDataPoint.java

@ -17,13 +17,12 @@ package org.thingsboard.server.edqs.data.dp;
import lombok.Getter; import lombok.Getter;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import org.thingsboard.common.util.TbBytePool;
import org.thingsboard.server.common.data.kv.DataType; import org.thingsboard.server.common.data.kv.DataType;
import org.thingsboard.server.edqs.util.TbBytePool;
import org.xerial.snappy.Snappy; import org.xerial.snappy.Snappy;
public class CompressedStringDataPoint extends AbstractDataPoint { public class CompressedStringDataPoint extends AbstractDataPoint {
public static final int MIN_STR_SIZE_TO_COMPRESS = 512;
@Getter @Getter
private final byte[] compressedValue; private final byte[] compressedValue;

6
common/edqs/src/main/java/org/thingsboard/server/edqs/data/dp/DoubleDataPoint.java

@ -46,6 +46,10 @@ public class DoubleDataPoint extends AbstractDataPoint {
@Override @Override
public int compareTo(DataPoint dataPoint) { public int compareTo(DataPoint dataPoint) {
return Double.compare(value, dataPoint.getDouble()); if (dataPoint.getType() == DataType.DOUBLE || dataPoint.getType() == DataType.LONG) {
return Double.compare(value, dataPoint.getDouble());
} else {
return super.compareTo(dataPoint);
}
} }
} }

2
common/edqs/src/main/java/org/thingsboard/server/edqs/data/dp/JsonDataPoint.java

@ -17,7 +17,7 @@ package org.thingsboard.server.edqs.data.dp;
import lombok.Getter; import lombok.Getter;
import org.thingsboard.server.common.data.kv.DataType; import org.thingsboard.server.common.data.kv.DataType;
import org.thingsboard.server.edqs.util.TbStringPool; import org.thingsboard.common.util.TbStringPool;
public class JsonDataPoint extends AbstractDataPoint { public class JsonDataPoint extends AbstractDataPoint {

8
common/edqs/src/main/java/org/thingsboard/server/edqs/data/dp/LongDataPoint.java

@ -51,6 +51,12 @@ public class LongDataPoint extends AbstractDataPoint {
@Override @Override
public int compareTo(DataPoint dataPoint) { public int compareTo(DataPoint dataPoint) {
return Long.compare(value, dataPoint.getLong()); if (dataPoint.getType() == DataType.DOUBLE) {
return Double.compare(getDouble(), dataPoint.getDouble());
} else if (dataPoint.getType() == DataType.LONG) {
return Long.compare(value, dataPoint.getLong());
} else {
return super.compareTo(dataPoint);
}
} }
} }

2
common/edqs/src/main/java/org/thingsboard/server/edqs/data/dp/StringDataPoint.java

@ -17,7 +17,7 @@ package org.thingsboard.server.edqs.data.dp;
import lombok.Getter; import lombok.Getter;
import org.thingsboard.server.common.data.kv.DataType; import org.thingsboard.server.common.data.kv.DataType;
import org.thingsboard.server.edqs.util.TbStringPool; import org.thingsboard.common.util.TbStringPool;
public class StringDataPoint extends AbstractDataPoint { public class StringDataPoint extends AbstractDataPoint {

5
common/edqs/src/main/java/org/thingsboard/server/edqs/repo/DefaultEdqsRepository.java

@ -27,10 +27,9 @@ 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.query.EntityCountQuery; import org.thingsboard.server.common.data.query.EntityCountQuery;
import org.thingsboard.server.common.data.query.EntityDataQuery; import org.thingsboard.server.common.data.query.EntityDataQuery;
import org.thingsboard.server.edqs.stats.EdqsStatsService; import org.thingsboard.server.common.stats.EdqsStatsService;
import org.thingsboard.server.queue.edqs.EdqsComponent; import org.thingsboard.server.queue.edqs.EdqsComponent;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.function.Predicate; import java.util.function.Predicate;
@ -42,7 +41,7 @@ import java.util.function.Predicate;
public class DefaultEdqsRepository implements EdqsRepository { public class DefaultEdqsRepository implements EdqsRepository {
private final static ConcurrentMap<TenantId, TenantRepo> repos = new ConcurrentHashMap<>(); private final static ConcurrentMap<TenantId, TenantRepo> repos = new ConcurrentHashMap<>();
private final Optional<EdqsStatsService> statsService; private final EdqsStatsService statsService;
public TenantRepo get(TenantId tenantId) { public TenantRepo get(TenantId tenantId) {
return repos.computeIfAbsent(tenantId, id -> new TenantRepo(id, statsService)); return repos.computeIfAbsent(tenantId, id -> new TenantRepo(id, statsService));

34
common/edqs/src/main/java/org/thingsboard/server/edqs/repo/TenantRepo.java

@ -25,7 +25,6 @@ import org.thingsboard.server.common.data.edqs.EdqsEventType;
import org.thingsboard.server.common.data.edqs.EdqsObject; import org.thingsboard.server.common.data.edqs.EdqsObject;
import org.thingsboard.server.common.data.edqs.Entity; import org.thingsboard.server.common.data.edqs.Entity;
import org.thingsboard.server.common.data.edqs.LatestTsKv; import org.thingsboard.server.common.data.edqs.LatestTsKv;
import org.thingsboard.server.common.data.edqs.fields.AssetFields;
import org.thingsboard.server.common.data.edqs.fields.EntityFields; import org.thingsboard.server.common.data.edqs.fields.EntityFields;
import org.thingsboard.server.common.data.edqs.query.QueryResult; import org.thingsboard.server.common.data.edqs.query.QueryResult;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
@ -41,6 +40,7 @@ import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.TsValue; import org.thingsboard.server.common.data.query.TsValue;
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;
import org.thingsboard.server.common.stats.EdqsStatsService;
import org.thingsboard.server.edqs.data.ApiUsageStateData; import org.thingsboard.server.edqs.data.ApiUsageStateData;
import org.thingsboard.server.edqs.data.AssetData; import org.thingsboard.server.edqs.data.AssetData;
import org.thingsboard.server.edqs.data.CustomerData; import org.thingsboard.server.edqs.data.CustomerData;
@ -55,9 +55,7 @@ import org.thingsboard.server.edqs.query.EdqsQuery;
import org.thingsboard.server.edqs.query.SortableEntityData; import org.thingsboard.server.edqs.query.SortableEntityData;
import org.thingsboard.server.edqs.query.processor.EntityQueryProcessor; import org.thingsboard.server.edqs.query.processor.EntityQueryProcessor;
import org.thingsboard.server.edqs.query.processor.EntityQueryProcessorFactory; import org.thingsboard.server.edqs.query.processor.EntityQueryProcessorFactory;
import org.thingsboard.server.edqs.stats.EdqsStatsService;
import org.thingsboard.server.edqs.util.RepositoryUtils; import org.thingsboard.server.edqs.util.RepositoryUtils;
import org.thingsboard.server.edqs.util.TbStringPool;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -65,7 +63,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.Optional;
import java.util.Set; import java.util.Set;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.UUID; import java.util.UUID;
@ -96,9 +93,9 @@ public class TenantRepo {
private final Lock entityUpdateLock = new ReentrantLock(); private final Lock entityUpdateLock = new ReentrantLock();
private final TenantId tenantId; private final TenantId tenantId;
private final Optional<EdqsStatsService> edqsStatsService; private final EdqsStatsService edqsStatsService;
public TenantRepo(TenantId tenantId, Optional<EdqsStatsService> edqsStatsService) { public TenantRepo(TenantId tenantId, EdqsStatsService edqsStatsService) {
this.tenantId = tenantId; this.tenantId = tenantId;
this.edqsStatsService = edqsStatsService; this.edqsStatsService = edqsStatsService;
} }
@ -144,9 +141,9 @@ public class TenantRepo {
RelationsRepo repo = relations.computeIfAbsent(entity.getTypeGroup(), tg -> new RelationsRepo()); RelationsRepo repo = relations.computeIfAbsent(entity.getTypeGroup(), tg -> new RelationsRepo());
EntityData<?> from = getOrCreate(entity.getFrom()); EntityData<?> from = getOrCreate(entity.getFrom());
EntityData<?> to = getOrCreate(entity.getTo()); EntityData<?> to = getOrCreate(entity.getTo());
boolean added = repo.add(from, to, TbStringPool.intern(entity.getType())); boolean added = repo.add(from, to, entity.getType());
if (added) { if (added) {
edqsStatsService.ifPresent(statService -> statService.reportEvent(tenantId, ObjectType.RELATION, EdqsEventType.UPDATED)); edqsStatsService.reportAdded(ObjectType.RELATION);
} }
} else if (RelationTypeGroup.DASHBOARD.equals(entity.getTypeGroup())) { } else if (RelationTypeGroup.DASHBOARD.equals(entity.getTypeGroup())) {
if (EntityRelation.CONTAINS_TYPE.equals(entity.getType()) && entity.getFrom().getEntityType() == EntityType.CUSTOMER) { if (EntityRelation.CONTAINS_TYPE.equals(entity.getType()) && entity.getFrom().getEntityType() == EntityType.CUSTOMER) {
@ -166,7 +163,7 @@ public class TenantRepo {
if (relationsRepo != null) { if (relationsRepo != null) {
boolean removed = relationsRepo.remove(entityRelation.getFrom().getId(), entityRelation.getTo().getId(), entityRelation.getType()); boolean removed = relationsRepo.remove(entityRelation.getFrom().getId(), entityRelation.getTo().getId(), entityRelation.getType());
if (removed) { if (removed) {
edqsStatsService.ifPresent(statService -> statService.reportEvent(tenantId, ObjectType.RELATION, EdqsEventType.DELETED)); edqsStatsService.reportRemoved(ObjectType.RELATION);
} }
} }
} else if (RelationTypeGroup.DASHBOARD.equals(entityRelation.getTypeGroup())) { } else if (RelationTypeGroup.DASHBOARD.equals(entityRelation.getTypeGroup())) {
@ -188,7 +185,6 @@ public class TenantRepo {
EntityType entityType = entity.getType(); EntityType entityType = entity.getType();
EntityData entityData = getOrCreate(entityType, entityId); EntityData entityData = getOrCreate(entityType, entityId);
processFields(fields);
EntityFields oldFields = entityData.getFields(); EntityFields oldFields = entityData.getFields();
entityData.setFields(fields); entityData.setFields(fields);
if (oldFields == null) { if (oldFields == null) {
@ -225,7 +221,7 @@ public class TenantRepo {
if (removed.getFields() != null) { if (removed.getFields() != null) {
getEntitySet(entityType).remove(removed); getEntitySet(entityType).remove(removed);
} }
edqsStatsService.ifPresent(statService -> statService.reportEvent(tenantId, ObjectType.fromEntityType(entityType), EdqsEventType.DELETED)); edqsStatsService.reportRemoved(entity.type());
UUID customerId = removed.getCustomerId(); UUID customerId = removed.getCustomerId();
if (customerId != null) { if (customerId != null) {
@ -246,7 +242,7 @@ public class TenantRepo {
Integer keyId = KeyDictionary.get(attributeKv.getKey()); Integer keyId = KeyDictionary.get(attributeKv.getKey());
boolean added = entityData.putAttr(keyId, attributeKv.getScope(), attributeKv.getDataPoint()); boolean added = entityData.putAttr(keyId, attributeKv.getScope(), attributeKv.getDataPoint());
if (added) { if (added) {
edqsStatsService.ifPresent(statService -> statService.reportEvent(tenantId, ObjectType.ATTRIBUTE_KV, EdqsEventType.UPDATED)); edqsStatsService.reportAdded(ObjectType.ATTRIBUTE_KV);
} }
} }
} }
@ -256,7 +252,7 @@ public class TenantRepo {
if (entityData != null) { if (entityData != null) {
boolean removed = entityData.removeAttr(KeyDictionary.get(attributeKv.getKey()), attributeKv.getScope()); boolean removed = entityData.removeAttr(KeyDictionary.get(attributeKv.getKey()), attributeKv.getScope());
if (removed) { if (removed) {
edqsStatsService.ifPresent(statService -> statService.reportEvent(tenantId, ObjectType.ATTRIBUTE_KV, EdqsEventType.DELETED)); edqsStatsService.reportRemoved(ObjectType.ATTRIBUTE_KV);
} }
} }
} }
@ -267,7 +263,7 @@ public class TenantRepo {
Integer keyId = KeyDictionary.get(latestTsKv.getKey()); Integer keyId = KeyDictionary.get(latestTsKv.getKey());
boolean added = entityData.putTs(keyId, latestTsKv.getDataPoint()); boolean added = entityData.putTs(keyId, latestTsKv.getDataPoint());
if (added) { if (added) {
edqsStatsService.ifPresent(statService -> statService.reportEvent(tenantId, ObjectType.LATEST_TS_KV, EdqsEventType.UPDATED)); edqsStatsService.reportAdded(ObjectType.LATEST_TS_KV);
} }
} }
} }
@ -277,17 +273,11 @@ public class TenantRepo {
if (entityData != null) { if (entityData != null) {
boolean removed = entityData.removeTs(KeyDictionary.get(latestTsKv.getKey())); boolean removed = entityData.removeTs(KeyDictionary.get(latestTsKv.getKey()));
if (removed) { if (removed) {
edqsStatsService.ifPresent(statService -> statService.reportEvent(tenantId, ObjectType.LATEST_TS_KV, EdqsEventType.DELETED)); edqsStatsService.reportRemoved(ObjectType.LATEST_TS_KV);
} }
} }
} }
public void processFields(EntityFields fields) {
if (fields instanceof AssetFields assetFields) {
assetFields.setType(TbStringPool.intern(assetFields.getType()));
}
}
public ConcurrentMap<UUID, EntityData<?>> getEntityMap(EntityType entityType) { public ConcurrentMap<UUID, EntityData<?>> getEntityMap(EntityType entityType) {
return entityMapByType.computeIfAbsent(entityType, et -> new ConcurrentHashMap<>()); return entityMapByType.computeIfAbsent(entityType, et -> new ConcurrentHashMap<>());
} }
@ -301,7 +291,7 @@ public class TenantRepo {
return getEntityMap(entityType).computeIfAbsent(entityId, id -> { return getEntityMap(entityType).computeIfAbsent(entityId, id -> {
log.debug("[{}] Adding {} {}", tenantId, entityType, id); log.debug("[{}] Adding {} {}", tenantId, entityType, id);
EntityData<?> entityData = constructEntityData(entityType, entityId); EntityData<?> entityData = constructEntityData(entityType, entityId);
edqsStatsService.ifPresent(statService -> statService.reportEvent(tenantId, ObjectType.fromEntityType(entityType), EdqsEventType.UPDATED)); edqsStatsService.reportAdded(ObjectType.fromEntityType(entityType));
return entityData; return entityData;
}); });
} }

92
common/edqs/src/main/java/org/thingsboard/server/edqs/stats/DefaultEdqsStatsService.java

@ -0,0 +1,92 @@
/**
* Copyright © 2016-2025 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.edqs.stats;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.ObjectType;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.query.EntityCountQuery;
import org.thingsboard.server.common.data.query.EntityDataQuery;
import org.thingsboard.server.common.stats.EdqsStatsService;
import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.common.stats.StatsTimer;
import org.thingsboard.server.common.stats.StatsType;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@Service
@Slf4j
@ConditionalOnExpression("'${queue.edqs.api.supported:true}' == 'true' && '${queue.edqs.stats.enabled:true}' == 'true'")
public class DefaultEdqsStatsService implements EdqsStatsService {
private final StatsFactory statsFactory;
@Value("${queue.edqs.stats.slow_query_threshold:3000}")
private int slowQueryThreshold;
private final ConcurrentHashMap<ObjectType, AtomicInteger> objectCounters = new ConcurrentHashMap<>();
private final StatsTimer dataQueryTimer;
private final StatsTimer countQueryTimer;
private DefaultEdqsStatsService(StatsFactory statsFactory) {
this.statsFactory = statsFactory;
dataQueryTimer = statsFactory.createTimer(StatsType.EDQS, "entityDataQueryTimer");
countQueryTimer = statsFactory.createTimer(StatsType.EDQS, "entityCountQueryTimer");
}
@Override
public void reportAdded(ObjectType objectType) {
getObjectCounter(objectType).incrementAndGet();
}
@Override
public void reportRemoved(ObjectType objectType) {
getObjectCounter(objectType).decrementAndGet();
}
@Override
public void reportDataQuery(TenantId tenantId, EntityDataQuery query, long timingNanos) {
double timingMs = timingNanos / 1000_000.0;
if (timingMs < slowQueryThreshold) {
log.debug("[{}] Executed data query in {} ms: {}", tenantId, timingMs, query);
} else {
log.warn("[{}] Executed slow data query in {} ms: {}", tenantId, timingMs, query);
}
dataQueryTimer.record(timingNanos, TimeUnit.NANOSECONDS);
}
@Override
public void reportCountQuery(TenantId tenantId, EntityCountQuery query, long timingNanos) {
double timingMs = timingNanos / 1000_000.0;
if (timingMs < slowQueryThreshold) {
log.debug("[{}] Executed count query in {} ms: {}", tenantId, timingMs, query);
} else {
log.warn("[{}] Executed slow count query in {} ms: {}", tenantId, timingMs, query);
}
countQueryTimer.record(timingNanos, TimeUnit.NANOSECONDS);
}
private AtomicInteger getObjectCounter(ObjectType objectType) {
return objectCounters.computeIfAbsent(objectType, type ->
statsFactory.createGauge("edqsObjectsCount", new AtomicInteger(), "objectType", type.name()));
}
}

81
common/edqs/src/main/java/org/thingsboard/server/edqs/stats/EdqsStatsService.java

@ -1,81 +0,0 @@
/**
* Copyright © 2016-2025 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.edqs.stats;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.ObjectType;
import org.thingsboard.server.common.data.edqs.EdqsEventType;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.common.stats.StatsType;
import org.thingsboard.server.queue.edqs.EdqsComponent;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
@EdqsComponent
@Service
@Slf4j
@RequiredArgsConstructor
@ConditionalOnProperty(name = "queue.edqs.stats.enabled", havingValue = "true", matchIfMissing = true)
public class EdqsStatsService {
private final ConcurrentHashMap<TenantId, EdqsStats> statsMap = new ConcurrentHashMap<>();
private final StatsFactory statsFactory;
public void reportEvent(TenantId tenantId, ObjectType objectType, EdqsEventType eventType) {
statsMap.computeIfAbsent(tenantId, id -> new EdqsStats(tenantId, statsFactory))
.reportEvent(objectType, eventType);
}
@Getter
@AllArgsConstructor
static class EdqsStats {
private final TenantId tenantId;
private final ConcurrentHashMap<ObjectType, AtomicInteger> entityCounters = new ConcurrentHashMap<>();
private final StatsFactory statsFactory;
private AtomicInteger getOrCreateObjectCounter(ObjectType objectType) {
return entityCounters.computeIfAbsent(objectType,
type -> statsFactory.createGauge(StatsType.EDQS.getName() + "_object_count", new AtomicInteger(),
"tenantId", tenantId.toString(), "objectType", type.name()));
}
@Override
public String toString() {
return entityCounters.entrySet().stream()
.map(counters -> counters.getKey().name()+ " total = [" + counters.getValue() + "]")
.collect(Collectors.joining(", "));
}
public void reportEvent(ObjectType objectType, EdqsEventType eventType) {
AtomicInteger objectCounter = getOrCreateObjectCounter(objectType);
if (eventType == EdqsEventType.UPDATED){
objectCounter.incrementAndGet();
} else if (eventType == EdqsEventType.DELETED) {
objectCounter.decrementAndGet();
}
}
}
}

45
common/edqs/src/main/java/org/thingsboard/server/edqs/util/EdqsConverter.java

@ -17,13 +17,19 @@ package org.thingsboard.server.edqs.util;
import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.google.protobuf.ByteString; import com.google.protobuf.ByteString;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.TbStringPool;
import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ObjectType; import org.thingsboard.server.common.data.ObjectType;
@ -49,6 +55,7 @@ import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.DataPointProto; import org.thingsboard.server.gen.transport.TransportProtos.DataPointProto;
import org.xerial.snappy.Snappy; import org.xerial.snappy.Snappy;
import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
@ -57,6 +64,9 @@ import java.util.UUID;
@Slf4j @Slf4j
public class EdqsConverter { public class EdqsConverter {
@Value("${queue.edqs.string_compression_length_threshold:512}")
private int stringCompressionLengthThreshold;
private final Map<ObjectType, Converter<? extends EdqsObject>> converters = new HashMap<>(); private final Map<ObjectType, Converter<? extends EdqsObject>> converters = new HashMap<>();
private final Converter<Entity> defaultConverter = new JsonConverter<>(Entity.class); private final Converter<Entity> defaultConverter = new JsonConverter<>(Entity.class);
@ -125,7 +135,7 @@ public class EdqsConverter {
}); });
} }
public static DataPointProto toDataPointProto(long ts, KvEntry kvEntry) { public DataPointProto toDataPointProto(long ts, KvEntry kvEntry) {
DataPointProto.Builder proto = DataPointProto.newBuilder(); DataPointProto.Builder proto = DataPointProto.newBuilder();
proto.setTs(ts); proto.setTs(ts);
switch (kvEntry.getDataType()) { switch (kvEntry.getDataType()) {
@ -134,7 +144,7 @@ public class EdqsConverter {
case DOUBLE -> proto.setDoubleV(kvEntry.getDoubleValue().get()); case DOUBLE -> proto.setDoubleV(kvEntry.getDoubleValue().get());
case STRING -> { case STRING -> {
String strValue = kvEntry.getStrValue().get(); String strValue = kvEntry.getStrValue().get();
if (strValue.length() < CompressedStringDataPoint.MIN_STR_SIZE_TO_COMPRESS) { if (strValue.length() < stringCompressionLengthThreshold) {
proto.setStringV(strValue); proto.setStringV(strValue);
} else { } else {
proto.setCompressedStringV(ByteString.copyFrom(compress(strValue))); proto.setCompressedStringV(ByteString.copyFrom(compress(strValue)));
@ -142,7 +152,7 @@ public class EdqsConverter {
} }
case JSON -> { case JSON -> {
String jsonValue = kvEntry.getJsonValue().get(); String jsonValue = kvEntry.getJsonValue().get();
if (jsonValue.length() < CompressedStringDataPoint.MIN_STR_SIZE_TO_COMPRESS) { if (jsonValue.length() < stringCompressionLengthThreshold) {
proto.setJsonV(jsonValue); proto.setJsonV(jsonValue);
} else { } else {
proto.setCompressedJsonV(ByteString.copyFrom(compress(jsonValue))); proto.setCompressedJsonV(ByteString.copyFrom(compress(jsonValue)));
@ -152,7 +162,7 @@ public class EdqsConverter {
return proto.build(); return proto.build();
} }
public static DataPoint fromDataPointProto(DataPointProto proto) { public DataPoint fromDataPointProto(DataPointProto proto) {
long ts = proto.getTs(); long ts = proto.getTs();
if (proto.hasBoolV()) { if (proto.hasBoolV()) {
return new BoolDataPoint(ts, proto.getBoolV()); return new BoolDataPoint(ts, proto.getBoolV());
@ -188,14 +198,6 @@ public class EdqsConverter {
return edqsEntity; return edqsEntity;
} }
public EdqsObject check(ObjectType type, Object object) {
if (object instanceof EdqsObject edqsObject) {
return edqsObject;
} else {
return toEntity(type.toEntityType(), object);
}
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@SneakyThrows @SneakyThrows
public <T extends EdqsObject> byte[] serialize(ObjectType type, T value) { public <T extends EdqsObject> byte[] serialize(ObjectType type, T value) {
@ -220,12 +222,18 @@ public class EdqsConverter {
@RequiredArgsConstructor @RequiredArgsConstructor
private static class JsonConverter<T> implements Converter<T> { private static class JsonConverter<T> implements Converter<T> {
private static final SimpleModule module = new SimpleModule();
private static final ObjectMapper mapper = JsonMapper.builder() private static final ObjectMapper mapper = JsonMapper.builder()
.visibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY) .visibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)
.visibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.NONE) .visibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.NONE)
.visibility(PropertyAccessor.IS_GETTER, JsonAutoDetect.Visibility.NONE) .visibility(PropertyAccessor.IS_GETTER, JsonAutoDetect.Visibility.NONE)
.build(); .build();
static {
module.addDeserializer(String.class, new InterningStringDeserializer());
mapper.registerModule(module);
}
private final Class<T> type; private final Class<T> type;
@SneakyThrows @SneakyThrows
@ -250,4 +258,17 @@ public class EdqsConverter {
} }
public static class InterningStringDeserializer extends StdDeserializer<String> {
public InterningStringDeserializer() {
super(String.class);
}
@Override
public String deserialize(JsonParser p, DeserializationContext ctx) throws IOException {
return TbStringPool.intern(p.getText());
}
}
} }

2
common/edqs/src/main/java/org/thingsboard/server/edqs/util/RepositoryUtils.java

@ -344,7 +344,7 @@ public class RepositoryUtils {
if (dp != null) { if (dp != null) {
return new TsValue(dp.getTs() > 0 ? dp.getTs() : ts, dp.valueToString()); return new TsValue(dp.getTs() > 0 ? dp.getTs() : ts, dp.valueToString());
} else { } else {
return new TsValue(ts, ""); return TsValue.EMPTY;
} }
} }

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

@ -38,7 +38,7 @@ public class TopicService {
@Value("${queue.rule-engine.notifications-topic:tb_rule_engine.notifications}") @Value("${queue.rule-engine.notifications-topic:tb_rule_engine.notifications}")
private String tbRuleEngineNotificationsTopic; private String tbRuleEngineNotificationsTopic;
@Value("${queue.transport.notifications-topics:tb_transport.notifications}") @Value("${queue.transport.notifications-topic:tb_transport.notifications}")
private String tbTransportNotificationsTopic; private String tbTransportNotificationsTopic;
@Value("${queue.edge.notifications-topic:tb_edge.notifications}") @Value("${queue.edge.notifications-topic:tb_edge.notifications}")
@ -47,7 +47,7 @@ public class TopicService {
@Value("${queue.edge.event-notifications-topic:tb_edge_event.notifications}") @Value("${queue.edge.event-notifications-topic:tb_edge_event.notifications}")
private String tbEdgeEventNotificationsTopic; private String tbEdgeEventNotificationsTopic;
@Value("${queue.calculated_fields.notifications-topic:calculated_field.notifications}") @Value("${queue.calculated-fields.notifications-topic:calculated_field.notifications}")
private String tbCalculatedFieldNotificationsTopic; private String tbCalculatedFieldNotificationsTopic;
private final ConcurrentMap<String, TopicPartitionInfo> tbCoreNotificationTopics = new ConcurrentHashMap<>(); private final ConcurrentMap<String, TopicPartitionInfo> tbCoreNotificationTopics = new ConcurrentHashMap<>();

24
common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaSettings.java

@ -50,8 +50,6 @@ import java.util.Properties;
@Component @Component
public class TbKafkaSettings { public class TbKafkaSettings {
private static final List<String> DYNAMIC_TOPICS = List.of("tb_edge_event.notifications");
@Value("${queue.kafka.bootstrap.servers}") @Value("${queue.kafka.bootstrap.servers}")
private String servers; private String servers;
@ -163,18 +161,20 @@ public class TbKafkaSettings {
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, ByteArrayDeserializer.class); props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, ByteArrayDeserializer.class);
consumerPropertiesPerTopic
.getOrDefault(topic, Collections.emptyList())
.forEach(kv -> props.put(kv.getKey(), kv.getValue()));
if (topic != null) { if (topic != null) {
DYNAMIC_TOPICS.stream() List<TbProperty> properties = consumerPropertiesPerTopic.get(topic);
.filter(topic::startsWith) if (properties == null) {
.findFirst() for (Map.Entry<String, List<TbProperty>> entry : consumerPropertiesPerTopic.entrySet()) {
.ifPresent(prefix -> consumerPropertiesPerTopic.getOrDefault(prefix, Collections.emptyList()) if (topic.startsWith(entry.getKey())) {
.forEach(kv -> props.put(kv.getKey(), kv.getValue()))); properties = entry.getValue();
break;
}
}
}
if (properties != null) {
properties.forEach(kv -> props.put(kv.getKey(), kv.getValue()));
}
} }
return props; return props;
} }

1
common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueProducerProvider.java

@ -115,7 +115,6 @@ public class TbCoreQueueProducerProvider implements TbQueueProducerProvider {
return toHousekeeper; return toHousekeeper;
} }
@Override @Override
public TbQueueProducer<TbProtoQueueMsg<ToEdgeMsg>> getTbEdgeMsgProducer() { public TbQueueProducer<TbProtoQueueMsg<ToEdgeMsg>> getTbEdgeMsgProducer() {
return toEdge; return toEdge;

41
common/stats/src/main/java/org/thingsboard/server/common/stats/DummyEdqsStatsService.java

@ -0,0 +1,41 @@
/**
* Copyright © 2016-2025 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.stats;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.ObjectType;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.query.EntityCountQuery;
import org.thingsboard.server.common.data.query.EntityDataQuery;
@Service
@ConditionalOnMissingBean(value = EdqsStatsService.class, ignored = DummyEdqsStatsService.class)
public class DummyEdqsStatsService implements EdqsStatsService {
@Override
public void reportAdded(ObjectType objectType) {}
@Override
public void reportRemoved(ObjectType objectType) {}
@Override
public void reportDataQuery(TenantId tenantId, EntityDataQuery query, long timingNanos) {}
@Override
public void reportCountQuery(TenantId tenantId, EntityCountQuery query, long timingNanos) {}
}

33
common/stats/src/main/java/org/thingsboard/server/common/stats/EdqsStatsService.java

@ -0,0 +1,33 @@
/**
* Copyright © 2016-2025 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.stats;
import org.thingsboard.server.common.data.ObjectType;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.query.EntityCountQuery;
import org.thingsboard.server.common.data.query.EntityDataQuery;
public interface EdqsStatsService {
void reportAdded(ObjectType objectType);
void reportRemoved(ObjectType objectType);
void reportDataQuery(TenantId tenantId, EntityDataQuery query, long timingNanos);
void reportCountQuery(TenantId tenantId, EntityCountQuery query, long timingNanos);
}

12
common/stats/src/main/java/org/thingsboard/server/common/stats/StatsTimer.java

@ -34,10 +34,14 @@ public class StatsTimer {
this.timer = micrometerTimer; this.timer = micrometerTimer;
} }
public void record(long timeMs) { public void record(long timeMs) {
record(timeMs, TimeUnit.MILLISECONDS);
}
public void record(long timing, TimeUnit timeUnit) {
count++; count++;
totalTime += timeMs; totalTime += timeUnit.toMillis(timing);
timer.record(timeMs, TimeUnit.MILLISECONDS); timer.record(timing, timeUnit);
} }
public double getAvg() { public double getAvg() {
@ -47,7 +51,7 @@ public class StatsTimer {
return (double) totalTime / count; return (double) totalTime / count;
} }
public void reset() { public void reset() {
count = 0; count = 0;
totalTime = 0; totalTime = 0;
} }

5
common/util/pom.xml

@ -111,6 +111,11 @@
<groupId>org.locationtech.jts</groupId> <groupId>org.locationtech.jts</groupId>
<artifactId>jts-core</artifactId> <artifactId>jts-core</artifactId>
</dependency> </dependency>
<dependency>
<groupId>net.objecthunter</groupId>
<artifactId>exp4j</artifactId>
<version>${exp4j.version}</version>
</dependency>
</dependencies> </dependencies>
<build> <build>

78
common/util/src/main/java/org/thingsboard/common/util/ExpressionFunctionsUtil.java

@ -0,0 +1,78 @@
/**
* Copyright © 2016-2025 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.common.util;
import net.objecthunter.exp4j.function.Function;
import net.objecthunter.exp4j.function.Functions;
import java.util.ArrayList;
import java.util.List;
public class ExpressionFunctionsUtil {
public static final List<Function> userDefinedFunctions = new ArrayList<>();
static {
userDefinedFunctions.add(
new Function("ln") {
@Override
public double apply(double... args) {
return Math.log(args[0]);
}
}
);
userDefinedFunctions.add(
new Function("lg") {
@Override
public double apply(double... args) {
return Math.log10(args[0]);
}
}
);
userDefinedFunctions.add(
new Function("logab", 2) {
@Override
public double apply(double... args) {
return Math.log(args[1]) / Math.log(args[0]);
}
}
);
userDefinedFunctions.add(Functions.getBuiltinFunction("sin"));
userDefinedFunctions.add(Functions.getBuiltinFunction("cos"));
userDefinedFunctions.add(Functions.getBuiltinFunction("tan"));
userDefinedFunctions.add(Functions.getBuiltinFunction("cot"));
userDefinedFunctions.add(Functions.getBuiltinFunction("log"));
userDefinedFunctions.add(Functions.getBuiltinFunction("log2"));
userDefinedFunctions.add(Functions.getBuiltinFunction("log10"));
userDefinedFunctions.add(Functions.getBuiltinFunction("log1p"));
userDefinedFunctions.add(Functions.getBuiltinFunction("abs"));
userDefinedFunctions.add(Functions.getBuiltinFunction("acos"));
userDefinedFunctions.add(Functions.getBuiltinFunction("asin"));
userDefinedFunctions.add(Functions.getBuiltinFunction("atan"));
userDefinedFunctions.add(Functions.getBuiltinFunction("cbrt"));
userDefinedFunctions.add(Functions.getBuiltinFunction("floor"));
userDefinedFunctions.add(Functions.getBuiltinFunction("sinh"));
userDefinedFunctions.add(Functions.getBuiltinFunction("sqrt"));
userDefinedFunctions.add(Functions.getBuiltinFunction("tanh"));
userDefinedFunctions.add(Functions.getBuiltinFunction("cosh"));
userDefinedFunctions.add(Functions.getBuiltinFunction("ceil"));
userDefinedFunctions.add(Functions.getBuiltinFunction("pow"));
userDefinedFunctions.add(Functions.getBuiltinFunction("exp"));
userDefinedFunctions.add(Functions.getBuiltinFunction("expm1"));
userDefinedFunctions.add(Functions.getBuiltinFunction("signum"));
}
}

2
common/edqs/src/main/java/org/thingsboard/server/edqs/util/TbBytePool.java → common/util/src/main/java/org/thingsboard/common/util/TbBytePool.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.edqs.util; package org.thingsboard.common.util;
import com.google.common.hash.Hashing; import com.google.common.hash.Hashing;
import org.springframework.util.ConcurrentReferenceHashMap; import org.springframework.util.ConcurrentReferenceHashMap;

2
common/edqs/src/main/java/org/thingsboard/server/edqs/util/TbStringPool.java → common/util/src/main/java/org/thingsboard/common/util/TbStringPool.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.edqs.util; package org.thingsboard.common.util;
import org.springframework.util.ConcurrentReferenceHashMap; import org.springframework.util.ConcurrentReferenceHashMap;

34
common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/GitRepository.java

@ -41,6 +41,7 @@ import org.eclipse.jgit.diff.HistogramDiff;
import org.eclipse.jgit.diff.RawText; import org.eclipse.jgit.diff.RawText;
import org.eclipse.jgit.diff.RawTextComparator; import org.eclipse.jgit.diff.RawTextComparator;
import org.eclipse.jgit.errors.LargeObjectException; import org.eclipse.jgit.errors.LargeObjectException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.ObjectLoader;
@ -147,22 +148,31 @@ public class GitRepository {
} }
public static GitRepository openOrClone(Path directory, RepositorySettings settings, boolean fetch) throws IOException, GitAPIException { public static GitRepository openOrClone(Path directory, RepositorySettings settings, boolean fetch) throws IOException, GitAPIException {
GitRepository repository;
if (GitRepository.exists(directory.toString())) { if (GitRepository.exists(directory.toString())) {
repository = GitRepository.open(directory.toFile(), settings); try {
if (fetch) { GitRepository repository = GitRepository.open(directory.toFile(), settings);
repository.fetch(); if (fetch) {
repository.fetch();
}
return repository;
} catch (RepositoryNotFoundException e) {
log.warn("{} not a git repository, reinitializing", directory);
} catch (org.eclipse.jgit.errors.TransportException | org.eclipse.jgit.api.errors.TransportException e) {
if (StringUtils.containsIgnoreCase(e.getMessage(), "missing commit")) {
log.warn("Couldn't fetch {} due to {}, reinitializing", directory, e.getMessage());
} else {
throw e;
}
} }
}
FileUtils.deleteDirectory(directory.toFile());
Files.createDirectories(directory);
if (settings.isLocalOnly()) {
return GitRepository.create(settings, directory.toFile());
} else { } else {
FileUtils.deleteDirectory(directory.toFile()); return GitRepository.clone(settings, directory.toFile());
Files.createDirectories(directory);
if (settings.isLocalOnly()) {
repository = GitRepository.create(settings, directory.toFile());
} else {
repository = GitRepository.clone(settings, directory.toFile());
}
} }
return repository;
} }
public static void test(RepositorySettings settings, File directory) throws Exception { public static void test(RepositorySettings settings, File directory) throws Exception {

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

@ -213,12 +213,11 @@ public class CustomerServiceImpl extends AbstractCachedEntityService<CustomerCac
@Override @Override
public Customer findOrCreatePublicCustomer(TenantId tenantId) { public Customer findOrCreatePublicCustomer(TenantId tenantId) {
log.trace("Executing findOrCreatePublicCustomer, tenantId [{}]", tenantId); log.trace("Executing findOrCreatePublicCustomer, tenantId [{}]", tenantId);
Validator.validateId(tenantId, id -> INCORRECT_TENANT_ID + id); var publicCustomer = findPublicCustomer(tenantId);
Optional<Customer> publicCustomerOpt = customerDao.findPublicCustomerByTenantId(tenantId.getId()); if (publicCustomer != null) {
if (publicCustomerOpt.isPresent()) { return publicCustomer;
return publicCustomerOpt.get();
} }
var publicCustomer = new Customer(); publicCustomer = new Customer();
publicCustomer.setTenantId(tenantId); publicCustomer.setTenantId(tenantId);
publicCustomer.setTitle(PUBLIC_CUSTOMER_TITLE); publicCustomer.setTitle(PUBLIC_CUSTOMER_TITLE);
try { try {
@ -230,7 +229,7 @@ public class CustomerServiceImpl extends AbstractCachedEntityService<CustomerCac
return saveCustomer(publicCustomer, false); return saveCustomer(publicCustomer, false);
} catch (DataValidationException e) { } catch (DataValidationException e) {
if (CUSTOMER_UNIQUE_TITLE_EX_MSG.equals(e.getMessage())) { if (CUSTOMER_UNIQUE_TITLE_EX_MSG.equals(e.getMessage())) {
publicCustomerOpt = customerDao.findPublicCustomerByTenantId(tenantId.getId()); Optional<Customer> publicCustomerOpt = customerDao.findPublicCustomerByTenantId(tenantId.getId());
if (publicCustomerOpt.isPresent()) { if (publicCustomerOpt.isPresent()) {
return publicCustomerOpt.get(); return publicCustomerOpt.get();
} }
@ -239,6 +238,14 @@ public class CustomerServiceImpl extends AbstractCachedEntityService<CustomerCac
} }
} }
@Override
public Customer findPublicCustomer(TenantId tenantId) {
log.trace("Executing findPublicCustomer, tenantId [{}]", tenantId);
Validator.validateId(tenantId, id -> INCORRECT_TENANT_ID + id);
Optional<Customer> publicCustomerOpt = customerDao.findPublicCustomerByTenantId(tenantId.getId());
return publicCustomerOpt.orElse(null);
}
@Override @Override
public PageData<Customer> findCustomersByTenantId(TenantId tenantId, PageLink pageLink) { public PageData<Customer> findCustomersByTenantId(TenantId tenantId, PageLink pageLink) {
log.trace("Executing findCustomersByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink); log.trace("Executing findCustomersByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink);

6
dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityServiceImpl.java

@ -41,7 +41,6 @@ import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.dao.settings.AdminSettingsService;
import org.thingsboard.server.dao.util.DeviceConnectivityUtil; import org.thingsboard.server.dao.util.DeviceConnectivityUtil;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.net.URISyntaxException; import java.net.URISyntaxException;
@ -86,6 +85,8 @@ public class DeviceConnectivityServiceImpl implements DeviceConnectivityService
private String mqttsPemCertFile; private String mqttsPemCertFile;
@Value("${device.connectivity.coaps.pem_cert_file:}") @Value("${device.connectivity.coaps.pem_cert_file:}")
private String coapsPemCertFile; private String coapsPemCertFile;
@Value("${device.connectivity.gateway.image_version:3.7-stable}")
private String gatewayImageVersion;
@Override @Override
public JsonNode findDevicePublishTelemetryCommands(String baseUrl, Device device) throws URISyntaxException { public JsonNode findDevicePublishTelemetryCommands(String baseUrl, Device device) throws URISyntaxException {
@ -157,7 +158,8 @@ public class DeviceConnectivityServiceImpl implements DeviceConnectivityService
String mqttType = isEnabled(MQTTS) ? MQTTS : MQTT; String mqttType = isEnabled(MQTTS) ? MQTTS : MQTT;
DeviceConnectivityInfo properties = getConnectivity(mqttType); DeviceConnectivityInfo properties = getConnectivity(mqttType);
DeviceCredentials creds = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId()); DeviceCredentials creds = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId());
return DeviceConnectivityUtil.getGatewayDockerComposeFile(baseUrl, properties, creds, mqttType); String host = getHost(baseUrl, properties, mqttType);
return DeviceConnectivityUtil.getGatewayDockerComposeFile(host, gatewayImageVersion, creds);
} }
private DeviceConnectivityInfo getConnectivity(String protocol) { private DeviceConnectivityInfo getConnectivity(String protocol) {

48
dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java

@ -20,6 +20,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.thingsboard.common.util.TbStopWatch;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.HasCustomerId; import org.thingsboard.server.common.data.HasCustomerId;
import org.thingsboard.server.common.data.HasEmail; import org.thingsboard.server.common.data.HasEmail;
@ -46,6 +47,7 @@ import org.thingsboard.server.common.data.query.EntityTypeFilter;
import org.thingsboard.server.common.data.query.KeyFilter; import org.thingsboard.server.common.data.query.KeyFilter;
import org.thingsboard.server.common.data.query.RelationsQueryFilter; import org.thingsboard.server.common.data.query.RelationsQueryFilter;
import org.thingsboard.server.common.msg.edqs.EdqsApiService; import org.thingsboard.server.common.msg.edqs.EdqsApiService;
import org.thingsboard.server.common.stats.EdqsStatsService;
import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.exception.IncorrectParameterException;
import java.util.ArrayList; import java.util.ArrayList;
@ -89,6 +91,9 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe
@Lazy @Lazy
private EdqsApiService edqsApiService; private EdqsApiService edqsApiService;
@Autowired
private EdqsStatsService edqsStatsService;
@Override @Override
public long countEntitiesByQuery(TenantId tenantId, CustomerId customerId, EntityCountQuery query) { public long countEntitiesByQuery(TenantId tenantId, CustomerId customerId, EntityCountQuery query) {
log.trace("Executing countEntitiesByQuery, tenantId [{}], customerId [{}], query [{}]", tenantId, customerId, query); log.trace("Executing countEntitiesByQuery, tenantId [{}], customerId [{}], query [{}]", tenantId, customerId, query);
@ -96,14 +101,19 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe
validateId(customerId, id -> INCORRECT_CUSTOMER_ID + id); validateId(customerId, id -> INCORRECT_CUSTOMER_ID + id);
validateEntityCountQuery(query); validateEntityCountQuery(query);
TbStopWatch stopWatch = TbStopWatch.create();
Long result;
if (edqsApiService.isEnabled() && validForEdqs(query) && !tenantId.isSysTenantId()) { if (edqsApiService.isEnabled() && validForEdqs(query) && !tenantId.isSysTenantId()) {
EdqsRequest request = EdqsRequest.builder() EdqsRequest request = EdqsRequest.builder()
.entityCountQuery(query) .entityCountQuery(query)
.build(); .build();
EdqsResponse response = processEdqsRequest(tenantId, customerId, request); EdqsResponse response = processEdqsRequest(tenantId, customerId, request);
return response.getEntityCountQueryResult(); result = response.getEntityCountQueryResult();
} else {
result = entityQueryDao.countEntitiesByQuery(tenantId, customerId, query);
} }
return this.entityQueryDao.countEntitiesByQuery(tenantId, customerId, query); edqsStatsService.reportCountQuery(tenantId, query, stopWatch.stopAndGetTotalTimeNanos());
return result;
} }
@Override @Override
@ -113,27 +123,31 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe
validateId(customerId, id -> INCORRECT_CUSTOMER_ID + id); validateId(customerId, id -> INCORRECT_CUSTOMER_ID + id);
validateEntityDataQuery(query); validateEntityDataQuery(query);
TbStopWatch stopWatch = TbStopWatch.create();
PageData<EntityData> result;
if (edqsApiService.isEnabled() && validForEdqs(query)) { if (edqsApiService.isEnabled() && validForEdqs(query)) {
EdqsRequest request = EdqsRequest.builder() EdqsRequest request = EdqsRequest.builder()
.entityDataQuery(query) .entityDataQuery(query)
.build(); .build();
EdqsResponse response = processEdqsRequest(tenantId, customerId, request); EdqsResponse response = processEdqsRequest(tenantId, customerId, request);
return response.getEntityDataQueryResult(); result = response.getEntityDataQueryResult();
} } else {
if (!isValidForOptimization(query)) {
if (!isValidForOptimization(query)) { result = entityQueryDao.findEntityDataByQuery(tenantId, customerId, query);
return this.entityQueryDao.findEntityDataByQuery(tenantId, customerId, query); } else {
} // 1 step - find entity data by filter and sort columns
PageData<EntityData> entityDataByQuery = findEntityIdsByFilterAndSorterColumns(tenantId, customerId, query);
// 1 step - find entity data by filter and sort columns if (entityDataByQuery == null || entityDataByQuery.getData().isEmpty()) {
PageData<EntityData> entityDataByQuery = findEntityIdsByFilterAndSorterColumns(tenantId, customerId, query); result = entityDataByQuery;
if (entityDataByQuery == null || entityDataByQuery.getData().isEmpty()) { } else {
return entityDataByQuery; // 2 step - find entity data by entity ids from the 1st step
List<EntityData> entities = fetchEntityDataByIdsFromInitialQuery(tenantId, customerId, query, entityDataByQuery.getData());
result = new PageData<>(entities, entityDataByQuery.getTotalPages(), entityDataByQuery.getTotalElements(), entityDataByQuery.hasNext());
}
}
} }
edqsStatsService.reportDataQuery(tenantId, query, stopWatch.stopAndGetTotalTimeNanos());
// 2 step - find entity data by entity ids from the 1st step return result;
List<EntityData> result = fetchEntityDataByIdsFromInitialQuery(tenantId, customerId, query, entityDataByQuery.getData());
return new PageData<>(result, entityDataByQuery.getTotalPages(), entityDataByQuery.getTotalElements(), entityDataByQuery.hasNext());
} }
private boolean validForEdqs(EntityCountQuery query) { // for compatibility with PE private boolean validForEdqs(EntityCountQuery query) { // for compatibility with PE

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save