Browse Source

Merge with master

pull/12325/head
Igor Kulikov 2 years ago
parent
commit
14731c3b8d
  1. 4
      application/pom.xml
  2. 8
      application/src/main/data/json/system/scada_symbols/dynamic-horizontal-scale-hp.svg
  3. 8
      application/src/main/data/json/system/scada_symbols/dynamic-vertical-scale-hp.svg
  4. 8
      application/src/main/data/json/system/scada_symbols/simple-horizontal-scale-hp.svg
  5. 8
      application/src/main/data/json/system/scada_symbols/simple-vertical-scale-hp.svg
  6. 30
      application/src/main/data/upgrade/basic/schema_update.sql
  7. 73
      application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
  8. 19
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java
  9. 2
      application/src/main/java/org/thingsboard/server/controller/AssetProfileController.java
  10. 1
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  11. 2
      application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java
  12. 2
      application/src/main/java/org/thingsboard/server/controller/QrCodeSettingsController.java
  13. 7
      application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java
  14. 7
      application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
  15. 9
      application/src/main/java/org/thingsboard/server/controller/RuleEngineController.java
  16. 163
      application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
  17. 2
      application/src/main/java/org/thingsboard/server/controller/WidgetsBundleController.java
  18. 9
      application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java
  19. 33
      application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java
  20. 36
      application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java
  21. 16
      application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java
  22. 84
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java
  23. 18
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java
  24. 9
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java
  25. 9
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java
  26. 70
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java
  27. 10
      application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java
  28. 204
      application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java
  29. 6
      application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java
  30. 26
      application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessor.java
  31. 4
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmTriggerProcessor.java
  32. 100
      application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java
  33. 11
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java
  34. 9
      application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbCoreDeviceRpcService.java
  35. 7
      application/src/main/java/org/thingsboard/server/service/rpc/TbRpcService.java
  36. 6
      application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java
  37. 6
      application/src/main/java/org/thingsboard/server/service/script/RuleNodeTbelScriptEngine.java
  38. 56
      application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java
  39. 22
      application/src/main/java/org/thingsboard/server/service/stats/DefaultRuleEngineStatisticsService.java
  40. 1
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/AssetProfileExportService.java
  41. 3
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DefaultEntityExportService.java
  42. 1
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DeviceProfileExportService.java
  43. 1
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/WidgetsBundleExportService.java
  44. 75
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java
  45. 25
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/BaseEntityImportService.java
  46. 13
      application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java
  47. 446
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java
  48. 31
      application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java
  49. 9
      application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java
  50. 30
      application/src/main/resources/thingsboard.yml
  51. 23
      application/src/test/java/org/thingsboard/server/actors/rule/DefaultTbContextTest.java
  52. 3
      application/src/test/java/org/thingsboard/server/controller/AuthControllerTest.java
  53. 19
      application/src/test/java/org/thingsboard/server/controller/ImageControllerTest.java
  54. 37
      application/src/test/java/org/thingsboard/server/controller/RuleEngineControllerTest.java
  55. 7
      application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java
  56. 66
      application/src/test/java/org/thingsboard/server/controller/WebsocketApiTest.java
  57. 16
      application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java
  58. 8
      application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java
  59. 8
      application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java
  60. 40
      application/src/test/java/org/thingsboard/server/service/queue/DefaultTbClusterServiceTest.java
  61. 7
      application/src/test/java/org/thingsboard/server/service/queue/ruleengine/TbRuleEngineQueueConsumerManagerTest.java
  62. 2
      application/src/test/java/org/thingsboard/server/service/queue/ruleengine/TbRuleEngineStrategyTest.java
  63. 7
      application/src/test/java/org/thingsboard/server/service/rpc/DefaultTbRuleEngineRpcServiceTest.java
  64. 16
      application/src/test/java/org/thingsboard/server/service/ruleengine/DefaultRuleEngineCallServiceTest.java
  65. 12
      application/src/test/java/org/thingsboard/server/service/sql/SequentialTimeseriesPersistenceTest.java
  66. 241
      application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java
  67. 28
      common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsSettings.java
  68. 6
      common/dao-api/src/main/java/org/thingsboard/server/dao/attributes/AttributesService.java
  69. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestService.java
  70. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ImageService.java
  71. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java
  72. 503
      common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java
  73. 2
      common/queue/src/main/java/org/thingsboard/server/queue/common/TbRuleEngineProducerService.java
  74. 11
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java
  75. 13
      common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/GitRepository.java
  76. 14
      dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java
  77. 10
      dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java
  78. 6
      dao/src/main/java/org/thingsboard/server/dao/domain/DomainServiceImpl.java
  79. 4
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRequestService.java
  80. 2
      dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestDao.java
  81. 10
      dao/src/main/java/org/thingsboard/server/dao/resource/BaseImageService.java
  82. 15
      dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java
  83. 32
      dao/src/main/java/org/thingsboard/server/dao/service/validator/DomainDataValidator.java
  84. 4
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRequestDao.java
  85. 2
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java
  86. 4
      dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java
  87. 2
      dao/src/main/resources/sql/schema-entities.sql
  88. 2
      dao/src/test/java/org/thingsboard/server/dao/service/DomainServiceTest.java
  89. 8
      docker/docker-upgrade-tb.sh
  90. 8
      msa/tb-node/docker/start-tb-node.sh
  91. 8
      msa/tb/docker/upgrade-tb.sh
  92. 8
      packaging/java/scripts/install/upgrade.sh
  93. 8
      packaging/java/scripts/install/upgrade_dev_db.sh
  94. 6
      packaging/java/scripts/windows/upgrade.bat
  95. 23
      pom.xml
  96. 117
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/AttributesDeleteRequest.java
  97. 128
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/AttributesSaveRequest.java
  98. 88
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java
  99. 3
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java
  100. 85
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TimeseriesDeleteRequest.java

4
application/pom.xml

@ -390,6 +390,10 @@
</resource> </resource>
</resources> </resources>
<plugins> <plugins>
<plugin>
<groupId>io.github.git-commit-id</groupId>
<artifactId>git-commit-id-maven-plugin</artifactId>
</plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>

8
application/src/main/data/json/system/scada_symbols/dynamic-horizontal-scale-hp.svg

@ -18,12 +18,12 @@
}, },
{ {
"tag": "highCriticalScale", "tag": "highCriticalScale",
"stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showHighCriticalScale = ctx.properties.showHighCriticalScale;\nvar highCriticalState = ctx.values.highCriticalState;\nif (showHighCriticalScale && highCriticalState !== null) {\n element.show();\n var offset = calculateOffset(highCriticalState, minValue, maxValue);\n element.width(653-offset);\n} else {\n element.hide();\n}\n\nif (showHighCriticalScale && value !== null && highCriticalState !== null) {\n if (value >= highCriticalState && value <= ctx.properties.maxValue) {\n element.fill(ctx.properties.activeCriticalScaleColor);\n } else {\n element.fill(ctx.properties.defaultCriticalScaleColor)\n }\n}", "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showHighCriticalScale = ctx.properties.showHighCriticalScale;\nvar highCriticalState = ctx.values.highCriticalState;\nif (showHighCriticalScale && highCriticalState !== null) {\n element.show();\n var offset = calculateOffset(highCriticalState, minValue, maxValue);\n element.width(653-offset);\n} else {\n element.hide();\n}\n\nif (showHighCriticalScale && value !== null && highCriticalState !== null) {\n if (value >= highCriticalState) {\n element.fill(ctx.properties.activeCriticalScaleColor);\n } else {\n element.fill(ctx.properties.defaultCriticalScaleColor)\n }\n}",
"actions": null "actions": null
}, },
{ {
"tag": "highWarningScale", "tag": "highWarningScale",
"stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showHighCriticalScale = ctx.properties.showHighCriticalScale;\nvar showHighWarningScale = ctx.properties.showHighWarningScale;\nvar highWarningState = ctx.values.highWarningState;\nvar highCriticalState = ctx.values.highCriticalState;\nif (showHighWarningScale && highWarningState !== null) {\n element.show();\n var offset = calculateOffset(highWarningState, minValue, maxValue);\n element.width(653-offset);\n} else {\n element.hide();\n}\n\nif (showHighWarningScale && value !== null) {\n if (!showHighCriticalScale) {\n highCriticalState = ctx.properties.maxValue;\n }\n \n if (highWarningState !== null && highCriticalState !== null) {\n if (value < highCriticalState && value >= highWarningState) {\n element.fill(ctx.properties.activeWarningScaleColor);\n } else {\n element.fill(ctx.properties.defaultWarningScaleColor);\n }\n }\n}", "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showHighCriticalScale = ctx.properties.showHighCriticalScale;\nvar showHighWarningScale = ctx.properties.showHighWarningScale;\nvar highWarningState = ctx.values.highWarningState;\nvar highCriticalState = ctx.values.highCriticalState;\nif (showHighWarningScale && highWarningState !== null) {\n element.show();\n var offset = calculateOffset(highWarningState, minValue, maxValue);\n element.width(653-offset);\n} else {\n element.hide();\n}\n\nif (showHighWarningScale && value !== null) {\n if (!showHighCriticalScale) {\n highCriticalState = Number.MAX_SAFE_INTEGER;\n }\n \n if (highWarningState !== null && highCriticalState !== null) {\n if (value < highCriticalState && value >= highWarningState) {\n element.fill(ctx.properties.activeWarningScaleColor);\n } else {\n element.fill(ctx.properties.defaultWarningScaleColor);\n }\n }\n}",
"actions": null "actions": null
}, },
{ {
@ -33,12 +33,12 @@
}, },
{ {
"tag": "lowCriticalScale", "tag": "lowCriticalScale",
"stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showLowCriticalScale = ctx.properties.showLowCriticalScale;\nvar lowCriticalValue = ctx.values.lowCriticalState;\n\nif (showLowCriticalScale && lowCriticalValue !== null) {\n element.show();\n var offset = calculateOffset(lowCriticalValue, minValue, maxValue);\n element.width(offset);\n} else {\n element.hide();\n}\n\nif (showLowCriticalScale && value !== null) {\n var lowCriticalScale = ctx.values.lowCriticalState;\n if (value <= lowCriticalScale && value >= ctx.properties.minValue) {\n element.fill(ctx.properties.activeCriticalScaleColor);\n } else {\n element.fill(ctx.properties.defaultCriticalScaleColor)\n }\n}", "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showLowCriticalScale = ctx.properties.showLowCriticalScale;\nvar lowCriticalValue = ctx.values.lowCriticalState;\n\nif (showLowCriticalScale && lowCriticalValue !== null) {\n element.show();\n var offset = calculateOffset(lowCriticalValue, minValue, maxValue);\n element.width(offset);\n} else {\n element.hide();\n}\n\nif (showLowCriticalScale && value !== null) {\n var lowCriticalScale = ctx.values.lowCriticalState;\n if (value <= lowCriticalScale) {\n element.fill(ctx.properties.activeCriticalScaleColor);\n } else {\n element.fill(ctx.properties.defaultCriticalScaleColor)\n }\n}",
"actions": null "actions": null
}, },
{ {
"tag": "lowWarningScale", "tag": "lowWarningScale",
"stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showLowWarningScale = ctx.properties.showLowWarningScale;\nvar showLowCriticalScale = ctx.properties.showLowCriticalScale;\nvar lowWarningState = ctx.values.lowWarningState;\nvar lowCriticalState = ctx.values.lowCriticalState;\nif (showLowWarningScale && lowWarningState !== null) {\n element.show();\n var offset = calculateOffset(lowWarningState, minValue, maxValue);\n element.width(offset);\n} else {\n element.hide();\n}\n\nif (showLowWarningScale && value !== null) {\n if (!showLowCriticalScale) {\n lowCriticalState = ctx.properties.minValue;\n }\n if (lowCriticalState !== null && lowWarningState !== null) {\n if (value > lowCriticalState && value <= lowWarningState) {\n element.fill(ctx.properties.activeWarningScaleColor);\n } else {\n element.fill(ctx.properties.defaultWarningScaleColor);\n }\n }\n}", "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showLowWarningScale = ctx.properties.showLowWarningScale;\nvar showLowCriticalScale = ctx.properties.showLowCriticalScale;\nvar lowWarningState = ctx.values.lowWarningState;\nvar lowCriticalState = ctx.values.lowCriticalState;\nif (showLowWarningScale && lowWarningState !== null) {\n element.show();\n var offset = calculateOffset(lowWarningState, minValue, maxValue);\n element.width(offset);\n} else {\n element.hide();\n}\n\nif (showLowWarningScale && value !== null) {\n if (!showLowCriticalScale) {\n lowCriticalState = Number.MIN_SAFE_INTEGER;\n }\n if (lowCriticalState !== null && lowWarningState !== null) {\n if (value > lowCriticalState && value <= lowWarningState) {\n element.fill(ctx.properties.activeWarningScaleColor);\n } else {\n element.fill(ctx.properties.defaultWarningScaleColor);\n }\n }\n}",
"actions": null "actions": null
}, },
{ {

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

8
application/src/main/data/json/system/scada_symbols/dynamic-vertical-scale-hp.svg

@ -18,12 +18,12 @@
}, },
{ {
"tag": "highCriticalScale", "tag": "highCriticalScale",
"stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showHighCriticalScale = ctx.properties.showHighCriticalScale;\nvar highCriticalState = ctx.values.highCriticalState;\nif (showHighCriticalScale && highCriticalState !== null) {\n element.show();\n var offset = calculateOffset(highCriticalState, minValue, maxValue);\n element.height(653-offset);\n} else {\n element.hide();\n}\n\nif (showHighCriticalScale && value !== null && highCriticalState !== null) {\n if (value >= highCriticalState && value <= ctx.properties.maxValue) {\n element.fill(ctx.properties.activeCriticalScaleColor);\n } else {\n element.fill(ctx.properties.defaultCriticalScaleColor)\n }\n}", "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showHighCriticalScale = ctx.properties.showHighCriticalScale;\nvar highCriticalState = ctx.values.highCriticalState;\nif (showHighCriticalScale && highCriticalState !== null) {\n element.show();\n var offset = calculateOffset(highCriticalState, minValue, maxValue);\n element.height(653-offset);\n} else {\n element.hide();\n}\n\nif (showHighCriticalScale && value !== null && highCriticalState !== null) {\n if (value >= highCriticalState) {\n element.fill(ctx.properties.activeCriticalScaleColor);\n } else {\n element.fill(ctx.properties.defaultCriticalScaleColor)\n }\n}",
"actions": null "actions": null
}, },
{ {
"tag": "highWarningScale", "tag": "highWarningScale",
"stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showHighCriticalScale = ctx.properties.showHighCriticalScale;\nvar showHighWarningScale = ctx.properties.showHighWarningScale;\nvar highWarningState = ctx.values.highWarningState;\nvar highCriticalState = ctx.values.highCriticalState;\nif (showHighWarningScale && highWarningState !== null) {\n element.show();\n var offset = calculateOffset(highWarningState, minValue, maxValue);\n element.height(653-offset);\n} else {\n element.hide();\n}\nif (showHighWarningScale && value !== null) {\n if (!showHighCriticalScale) {\n highCriticalState = ctx.properties.maxValue;\n }\n if (highWarningState !== null && highCriticalState !== null) {\n if (value < highCriticalState && value >= highWarningState) {\n element.fill(ctx.properties.activeWarningScaleColor);\n } else {\n element.fill(ctx.properties.defaultWarningScaleColor);\n }\n }\n}", "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showHighCriticalScale = ctx.properties.showHighCriticalScale;\nvar showHighWarningScale = ctx.properties.showHighWarningScale;\nvar highWarningState = ctx.values.highWarningState;\nvar highCriticalState = ctx.values.highCriticalState;\nif (showHighWarningScale && highWarningState !== null) {\n element.show();\n var offset = calculateOffset(highWarningState, minValue, maxValue);\n element.height(653-offset);\n} else {\n element.hide();\n}\nif (showHighWarningScale && value !== null) {\n if (!showHighCriticalScale) {\n highCriticalState = Number.MAX_SAFE_INTEGER;\n }\n if (highWarningState !== null && highCriticalState !== null) {\n if (value < highCriticalState && value >= highWarningState) {\n element.fill(ctx.properties.activeWarningScaleColor);\n } else {\n element.fill(ctx.properties.defaultWarningScaleColor);\n }\n }\n}",
"actions": null "actions": null
}, },
{ {
@ -33,12 +33,12 @@
}, },
{ {
"tag": "lowCriticalScale", "tag": "lowCriticalScale",
"stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showLowCriticalScale = ctx.properties.showLowCriticalScale;\nvar lowCriticalValue = ctx.values.lowCriticalState;\n\nif (showLowCriticalScale && lowCriticalValue !== null) {\n element.show();\n var offset = calculateOffset(lowCriticalValue, minValue, maxValue);\n element.height(offset);\n} else {\n element.hide();\n}\nif (showLowCriticalScale && value !== null) {\n var lowCriticalScale = ctx.values.lowCriticalState;\n if (value <= lowCriticalScale && value >= ctx.properties.minValue) {\n element.fill(ctx.properties.activeCriticalScaleColor);\n } else {\n element.fill(ctx.properties.defaultCriticalScaleColor)\n }\n}", "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showLowCriticalScale = ctx.properties.showLowCriticalScale;\nvar lowCriticalValue = ctx.values.lowCriticalState;\n\nif (showLowCriticalScale && lowCriticalValue !== null) {\n element.show();\n var offset = calculateOffset(lowCriticalValue, minValue, maxValue);\n element.height(offset);\n} else {\n element.hide();\n}\nif (showLowCriticalScale && value !== null) {\n var lowCriticalScale = ctx.values.lowCriticalState;\n if (value <= lowCriticalScale) {\n element.fill(ctx.properties.activeCriticalScaleColor);\n } else {\n element.fill(ctx.properties.defaultCriticalScaleColor)\n }\n}",
"actions": null "actions": null
}, },
{ {
"tag": "lowWarningScale", "tag": "lowWarningScale",
"stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showLowWarningScale = ctx.properties.showLowWarningScale;\nvar showLowCriticalScale = ctx.properties.showLowCriticalScale;\nvar lowWarningState = ctx.values.lowWarningState;\nvar lowCriticalState = ctx.values.lowCriticalState;\nif (showLowWarningScale && lowWarningState !== null) {\n element.show();\n var offset = calculateOffset(lowWarningState, minValue, maxValue);\n element.height(offset);\n} else {\n element.hide();\n}\nif (showLowWarningScale && value !== null) {\n if (!showLowCriticalScale) {\n lowCriticalState = ctx.properties.minValue;\n }\n if (lowCriticalState !== null && lowWarningState !== null) {\n if (value > lowCriticalState && value <= lowWarningState) {\n element.fill(ctx.properties.activeWarningScaleColor);\n } else {\n element.fill(ctx.properties.defaultWarningScaleColor);\n }\n }\n}", "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showLowWarningScale = ctx.properties.showLowWarningScale;\nvar showLowCriticalScale = ctx.properties.showLowCriticalScale;\nvar lowWarningState = ctx.values.lowWarningState;\nvar lowCriticalState = ctx.values.lowCriticalState;\nif (showLowWarningScale && lowWarningState !== null) {\n element.show();\n var offset = calculateOffset(lowWarningState, minValue, maxValue);\n element.height(offset);\n} else {\n element.hide();\n}\nif (showLowWarningScale && value !== null) {\n if (!showLowCriticalScale) {\n lowCriticalState = Number.MIN_SAFE_INTEGER;\n }\n if (lowCriticalState !== null && lowWarningState !== null) {\n if (value > lowCriticalState && value <= lowWarningState) {\n element.fill(ctx.properties.activeWarningScaleColor);\n } else {\n element.fill(ctx.properties.defaultWarningScaleColor);\n }\n }\n}",
"actions": null "actions": null
}, },
{ {

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

8
application/src/main/data/json/system/scada_symbols/simple-horizontal-scale-hp.svg

@ -18,12 +18,12 @@
}, },
{ {
"tag": "highCriticalScale", "tag": "highCriticalScale",
"stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n return offset;\n}\n\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showHighCriticalScale = ctx.properties.showHighCriticalScale;\nvar highCriticalValue = ctx.properties.highCriticalScale;\nvar value = ctx.values.value;\n\nif (showHighCriticalScale) {\n element.show();\n var offset = calculateOffset(highCriticalValue, minValue, maxValue);\n element.width(653-offset);\n} else {\n element.hide();\n}\n\nif (showHighCriticalScale && value !== null) {\n var highCriticalScale = ctx.properties.highCriticalScale;\n if (value >= highCriticalScale && value <= ctx.properties.maxValue) {\n element.fill(ctx.properties.activeCriticalScaleColor);\n } else {\n element.fill(ctx.properties.defaultCriticalScaleColor)\n }\n}", "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n return offset;\n}\n\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showHighCriticalScale = ctx.properties.showHighCriticalScale;\nvar highCriticalValue = ctx.properties.highCriticalScale;\nvar value = ctx.values.value;\n\nif (showHighCriticalScale) {\n element.show();\n var offset = calculateOffset(highCriticalValue, minValue, maxValue);\n element.width(653-offset);\n} else {\n element.hide();\n}\nif (showHighCriticalScale && value !== null) {\n var highCriticalScale = ctx.properties.highCriticalScale;\n if (value >= highCriticalScale) {\n element.fill(ctx.properties.activeCriticalScaleColor);\n } else {\n element.fill(ctx.properties.defaultCriticalScaleColor)\n }\n}",
"actions": null "actions": null
}, },
{ {
"tag": "highWarningScale", "tag": "highWarningScale",
"stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n return offset;\n}\n\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showHighWarningScale = ctx.properties.showHighWarningScale;\nvar showHighCriticalScale = ctx.properties.showHighCriticalScale;\nvar highWarningValue = ctx.properties.highWarningScale;\nvar value = ctx.values.value;\n\nif (showHighWarningScale) {\n element.show();\n var offset = calculateOffset(highWarningValue, minValue, maxValue);\n element.width(653-offset);\n} else {\n element.hide();\n}\n\nif (showHighWarningScale && value !== null) {\n var highCriticalScale = ctx.properties.highCriticalScale;\n if (!showHighCriticalScale) {\n highCriticalScale = ctx.properties.maxValue;\n }\n var highWarningScale = ctx.properties.highWarningScale;\n if (value < highCriticalScale && value >= highWarningScale) {\n element.fill(ctx.properties.activeWarningScaleColor);\n } else {\n element.fill(ctx.properties.defaultWarningScaleColor);\n }\n}", "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n return offset;\n}\n\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showHighWarningScale = ctx.properties.showHighWarningScale;\nvar showHighCriticalScale = ctx.properties.showHighCriticalScale;\nvar highWarningValue = ctx.properties.highWarningScale;\nvar value = ctx.values.value;\n\nif (showHighWarningScale) {\n element.show();\n var offset = calculateOffset(highWarningValue, minValue, maxValue);\n element.width(653-offset);\n} else {\n element.hide();\n}\n\nif (showHighWarningScale && value !== null) {\n var highCriticalScale = ctx.properties.highCriticalScale;\n if (!showHighCriticalScale) {\n highCriticalScale = Number.MAX_SAFE_INTEGER;\n }\n var highWarningScale = ctx.properties.highWarningScale;\n if (value < highCriticalScale && value >= highWarningScale) {\n element.fill(ctx.properties.activeWarningScaleColor);\n } else {\n element.fill(ctx.properties.defaultWarningScaleColor);\n }\n}",
"actions": null "actions": null
}, },
{ {
@ -33,12 +33,12 @@
}, },
{ {
"tag": "lowCriticalScale", "tag": "lowCriticalScale",
"stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showLowCriticalScale = ctx.properties.showLowCriticalScale;\nvar lowCriticalScale = ctx.properties.lowCriticalScale;\n\nif (showLowCriticalScale) {\n element.show();\n var offset = calculateOffset(lowCriticalScale, minValue, maxValue);\n var childrenElement = element.children();\n element.width(offset);\n} else {\n element.hide();\n}\n\nif (showLowCriticalScale && value !== null) {\n if (value <= lowCriticalScale && value >= ctx.properties.minValue) {\n element.fill(ctx.properties.activeCriticalScaleColor);\n } else {\n element.fill(ctx.properties.defaultCriticalScaleColor)\n }\n}", "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showLowCriticalScale = ctx.properties.showLowCriticalScale;\nvar lowCriticalScale = ctx.properties.lowCriticalScale;\n\nif (showLowCriticalScale) {\n element.show();\n var offset = calculateOffset(lowCriticalScale, minValue, maxValue);\n var childrenElement = element.children();\n element.width(offset);\n} else {\n element.hide();\n}\n\nif (showLowCriticalScale && value !== null) {\n if (value <= lowCriticalScale) {\n element.fill(ctx.properties.activeCriticalScaleColor);\n } else {\n element.fill(ctx.properties.defaultCriticalScaleColor)\n }\n}",
"actions": null "actions": null
}, },
{ {
"tag": "lowWarningScale", "tag": "lowWarningScale",
"stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showLowWarningScale = ctx.properties.showLowWarningScale;\nvar showLowCriticalScale = ctx.properties.showLowCriticalScale;\nvar lowWarningScale = ctx.properties.lowWarningScale;\nif (showLowWarningScale) {\n element.show();\n var offset = calculateOffset(lowWarningScale, minValue, maxValue);\n element.width(offset);\n} else {\n element.hide();\n}\n\nif (showLowWarningScale && value !== null) {\n var lowCriticalScale = ctx.properties.lowCriticalScale;\n if (!showLowCriticalScale) {\n lowCriticalScale = ctx.properties.minValue;\n }\n if (value > lowCriticalScale && value <= lowWarningScale) {\n element.fill(ctx.properties.activeWarningScaleColor);\n } else {\n element.fill(ctx.properties.defaultWarningScaleColor);\n }\n}", "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showLowWarningScale = ctx.properties.showLowWarningScale;\nvar showLowCriticalScale = ctx.properties.showLowCriticalScale;\nvar lowWarningScale = ctx.properties.lowWarningScale;\nif (showLowWarningScale) {\n element.show();\n var offset = calculateOffset(lowWarningScale, minValue, maxValue);\n element.width(offset);\n} else {\n element.hide();\n}\n\nif (showLowWarningScale && value !== null) {\n var lowCriticalScale = ctx.properties.lowCriticalScale;\n if (!showLowCriticalScale) {\n lowCriticalScale = Number.MIN_SAFE_INTEGER;\n }\n if (value > lowCriticalScale && value <= lowWarningScale) {\n element.fill(ctx.properties.activeWarningScaleColor);\n } else {\n element.fill(ctx.properties.defaultWarningScaleColor);\n }\n}",
"actions": null "actions": null
}, },
{ {

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

8
application/src/main/data/json/system/scada_symbols/simple-vertical-scale-hp.svg

@ -18,12 +18,12 @@
}, },
{ {
"tag": "highCriticalScale", "tag": "highCriticalScale",
"stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showHighCriticalScale = ctx.properties.showHighCriticalScale;\nvar highCriticalScale = ctx.properties.highCriticalScale;\n\nif (showHighCriticalScale) {\n element.show();\n var offset = calculateOffset(highCriticalScale, minValue, maxValue);\n element.height(653-offset);\n} else {\n element.hide();\n}\n\nif (showHighCriticalScale && value !== null) {\n var highCriticalScale = ctx.properties.highCriticalScale;\n if (value >= highCriticalScale && value <= ctx.properties.maxValue) {\n element.fill(ctx.properties.activeCriticalScaleColor);\n } else {\n element.fill(ctx.properties.defaultCriticalScaleColor)\n }\n}", "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showHighCriticalScale = ctx.properties.showHighCriticalScale;\nvar highCriticalScale = ctx.properties.highCriticalScale;\n\nif (showHighCriticalScale) {\n element.show();\n var offset = calculateOffset(highCriticalScale, minValue, maxValue);\n element.height(653-offset);\n} else {\n element.hide();\n}\n\nif (showHighCriticalScale && value !== null) {\n var highCriticalScale = ctx.properties.highCriticalScale;\n if (value >= highCriticalScale) {\n element.fill(ctx.properties.activeCriticalScaleColor);\n } else {\n element.fill(ctx.properties.defaultCriticalScaleColor)\n }\n}",
"actions": null "actions": null
}, },
{ {
"tag": "highWarningScale", "tag": "highWarningScale",
"stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showHighWarningScale = ctx.properties.showHighWarningScale;\nvar showHighCriticalScale = ctx.properties.showHighCriticalScale;\nvar highWarningValue = ctx.properties.highWarningScale;\nif (showHighWarningScale) {\n element.show();\n var offset = calculateOffset(highWarningValue, minValue, maxValue);\n element.height(653-offset);\n} else {\n element.hide();\n}\nif (showHighWarningScale && value !== null) {\n var highCriticalScale = ctx.properties.highCriticalScale;\n if (!showHighCriticalScale) {\n highCriticalScale = ctx.properties.maxValue;\n }\n var highWarningScale = ctx.properties.highWarningScale;\n if (value < highCriticalScale && value >= highWarningScale) {\n element.fill(ctx.properties.activeWarningScaleColor);\n } else {\n element.fill(ctx.properties.defaultWarningScaleColor);\n }\n}", "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showHighWarningScale = ctx.properties.showHighWarningScale;\nvar showHighCriticalScale = ctx.properties.showHighCriticalScale;\nvar highWarningValue = ctx.properties.highWarningScale;\nif (showHighWarningScale) {\n element.show();\n var offset = calculateOffset(highWarningValue, minValue, maxValue);\n element.height(653-offset);\n} else {\n element.hide();\n}\nif (showHighWarningScale && value !== null) {\n var highCriticalScale = ctx.properties.highCriticalScale;\n if (!showHighCriticalScale) {\n highCriticalScale = Number.MAX_SAFE_INTEGER;\n }\n var highWarningScale = ctx.properties.highWarningScale;\n if (value < highCriticalScale && value >= highWarningScale) {\n element.fill(ctx.properties.activeWarningScaleColor);\n } else {\n element.fill(ctx.properties.defaultWarningScaleColor);\n }\n}",
"actions": null "actions": null
}, },
{ {
@ -33,12 +33,12 @@
}, },
{ {
"tag": "lowCriticalScale", "tag": "lowCriticalScale",
"stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showLowCriticalScale = ctx.properties.showLowCriticalScale;\nvar lowCriticalScale = ctx.properties.lowCriticalScale;\nif (showLowCriticalScale) {\n element.show();\n var offset = calculateOffset(lowCriticalScale, minValue, maxValue);\n element.height(offset);\n} else {\n element.hide();\n}\n\nif (showLowCriticalScale && value !== null) {\n if (value <= lowCriticalScale && value >= ctx.properties.minValue) {\n element.fill(ctx.properties.activeCriticalScaleColor);\n } else {\n element.fill(ctx.properties.defaultCriticalScaleColor)\n }\n}", "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showLowCriticalScale = ctx.properties.showLowCriticalScale;\nvar lowCriticalScale = ctx.properties.lowCriticalScale;\nif (showLowCriticalScale) {\n element.show();\n var offset = calculateOffset(lowCriticalScale, minValue, maxValue);\n element.height(offset);\n} else {\n element.hide();\n}\n\nif (showLowCriticalScale && value !== null) {\n if (value <= lowCriticalScale) {\n element.fill(ctx.properties.activeCriticalScaleColor);\n } else {\n element.fill(ctx.properties.defaultCriticalScaleColor)\n }\n}",
"actions": null "actions": null
}, },
{ {
"tag": "lowWarningScale", "tag": "lowWarningScale",
"stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showLowWarningScale = ctx.properties.showLowWarningScale;\nvar showLowCriticalScale = ctx.properties.showLowCriticalScale;\nvar lowWarningScale = ctx.properties.lowWarningScale;\nif (showLowWarningScale) {\n element.show();\n var offset = calculateOffset(lowWarningScale, minValue, maxValue);\n element.height(offset);\n} else {\n element.hide();\n}\n\nif (showLowWarningScale && value !== null) {\n var lowCriticalScale = ctx.properties.lowCriticalScale;\n if (!showLowCriticalScale) {\n lowCriticalScale = ctx.properties.minValue;\n }\n if (value > lowCriticalScale && value <= lowWarningScale) {\n element.fill(ctx.properties.activeWarningScaleColor);\n } else {\n element.fill(ctx.properties.defaultWarningScaleColor);\n }\n}", "stateRenderFunction": "function calculateOffset(value, minValue, maxValue) {\n var clampedValue = Math.max(minValue, Math.min(value, maxValue));\n var normalizedValue = (clampedValue - minValue) / (maxValue - minValue);\n var offset = normalizedValue * 653;\n return offset;\n}\n\nvar value = ctx.values.value;\nvar minValue = ctx.properties.minValue;\nvar maxValue = ctx.properties.maxValue;\nvar showLowWarningScale = ctx.properties.showLowWarningScale;\nvar showLowCriticalScale = ctx.properties.showLowCriticalScale;\nvar lowWarningScale = ctx.properties.lowWarningScale;\nif (showLowWarningScale) {\n element.show();\n var offset = calculateOffset(lowWarningScale, minValue, maxValue);\n element.height(offset);\n} else {\n element.hide();\n}\nif (showLowWarningScale && value !== null) {\n var lowCriticalScale = ctx.properties.lowCriticalScale;\n if (!showLowCriticalScale) {\n lowCriticalScale = Number.MIN_SAFE_INTEGER;\n }\n if (value > lowCriticalScale && value <= lowWarningScale) {\n element.fill(ctx.properties.activeWarningScaleColor);\n } else {\n element.fill(ctx.properties.defaultWarningScaleColor);\n }\n}",
"actions": null "actions": null
}, },
{ {

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

30
application/src/main/data/upgrade/basic/schema_update.sql

@ -143,7 +143,7 @@ $$
LOOP LOOP
generatedBundleId := NULL; generatedBundleId := NULL;
-- migrate android config -- migrate android config
IF (qrCodeRecord.android_config IS NOT NULL AND qrCodeRecord.android_config::jsonb -> 'appPackage' IS NOT NULL) THEN IF (qrCodeRecord.android_config::jsonb ->> 'appPackage' IS NOT NULL) THEN
androidPkgName := qrCodeRecord.android_config::jsonb ->> 'appPackage'; androidPkgName := qrCodeRecord.android_config::jsonb ->> 'appPackage';
SELECT id into androidAppId FROM mobile_app WHERE pkg_name = androidPkgName AND platform_type = 'ANDROID'; SELECT id into androidAppId FROM mobile_app WHERE pkg_name = androidPkgName AND platform_type = 'ANDROID';
IF androidAppId IS NULL THEN IF androidAppId IS NULL THEN
@ -154,17 +154,16 @@ $$
generatedBundleId := uuid_generate_v4(); generatedBundleId := uuid_generate_v4();
INSERT INTO mobile_app_bundle(id, created_time, tenant_id, title, android_app_id) INSERT INTO mobile_app_bundle(id, created_time, tenant_id, title, android_app_id)
VALUES (generatedBundleId, (extract(epoch from now()) * 1000), qrCodeRecord.tenant_id, androidPkgName || ' (autogenerated)', androidAppId); VALUES (generatedBundleId, (extract(epoch from now()) * 1000), qrCodeRecord.tenant_id, androidPkgName || ' (autogenerated)', androidAppId);
UPDATE qr_code_settings SET mobile_app_bundle_id = generatedBundleId, UPDATE qr_code_settings SET mobile_app_bundle_id = generatedBundleId;
android_enabled = (qrCodeRecord.android_config::jsonb ->> 'enabled')::boolean WHERE id = qrCodeRecord.id;
ELSE ELSE
UPDATE mobile_app SET store_info = qrCodeRecord.android_config::jsonb - 'appPackage' - 'enabled' WHERE id = androidAppId; UPDATE mobile_app SET store_info = qrCodeRecord.android_config::jsonb - 'appPackage' - 'enabled' WHERE id = androidAppId;
UPDATE qr_code_settings SET mobile_app_bundle_id = (SELECT id FROM mobile_app_bundle WHERE mobile_app_bundle.android_app_id = androidAppId), UPDATE qr_code_settings SET mobile_app_bundle_id = (SELECT id FROM mobile_app_bundle WHERE mobile_app_bundle.android_app_id = androidAppId);
android_enabled = (qrCodeRecord.android_config::jsonb ->> 'enabled')::boolean WHERE id = qrCodeRecord.id;
END IF; END IF;
END IF; END IF;
UPDATE qr_code_settings SET android_enabled = (qrCodeRecord.android_config::jsonb ->> 'enabled')::boolean WHERE id = qrCodeRecord.id;
-- migrate ios config -- migrate ios config
IF (qrCodeRecord.ios_config IS NOT NULL AND qrCodeRecord.ios_config::jsonb -> 'appId' IS NOT NULL) THEN IF (qrCodeRecord.ios_config::jsonb ->> 'appId' IS NOT NULL) THEN
iosPkgName := substring(qrCodeRecord.ios_config::jsonb ->> 'appId', strpos(qrCodeRecord.ios_config::jsonb ->> 'appId', '.') + 1); iosPkgName := substring(qrCodeRecord.ios_config::jsonb ->> 'appId', strpos(qrCodeRecord.ios_config::jsonb ->> 'appId', '.') + 1);
SELECT id INTO iosAppId FROM mobile_app WHERE pkg_name = iosPkgName AND platform_type = 'IOS'; SELECT id INTO iosAppId FROM mobile_app WHERE pkg_name = iosPkgName AND platform_type = 'IOS';
IF iosAppId IS NULL THEN IF iosAppId IS NULL THEN
@ -176,24 +175,35 @@ $$
generatedBundleId := uuid_generate_v4(); generatedBundleId := uuid_generate_v4();
INSERT INTO mobile_app_bundle(id, created_time, tenant_id, title, ios_app_id) INSERT INTO mobile_app_bundle(id, created_time, tenant_id, title, ios_app_id)
VALUES (generatedBundleId, (extract(epoch from now()) * 1000), qrCodeRecord.tenant_id, iosPkgName || ' (autogenerated)', iosAppId); VALUES (generatedBundleId, (extract(epoch from now()) * 1000), qrCodeRecord.tenant_id, iosPkgName || ' (autogenerated)', iosAppId);
UPDATE qr_code_settings SET mobile_app_bundle_id = generatedBundleId, UPDATE qr_code_settings SET mobile_app_bundle_id = generatedBundleId;
ios_enabled = (qrCodeRecord.ios_config::jsonb ->> 'enabled')::boolean WHERE id = qrCodeRecord.id;
ELSE ELSE
UPDATE mobile_app_bundle SET ios_app_id = iosAppId WHERE id = generatedBundleId; UPDATE mobile_app_bundle SET ios_app_id = iosAppId WHERE id = generatedBundleId;
END IF; END IF;
ELSE ELSE
UPDATE qr_code_settings SET mobile_app_bundle_id = (SELECT id FROM mobile_app_bundle WHERE mobile_app_bundle.ios_app_id = iosAppId), UPDATE qr_code_settings SET mobile_app_bundle_id = (SELECT id FROM mobile_app_bundle WHERE mobile_app_bundle.ios_app_id = iosAppId);
ios_enabled = (qrCodeRecord.ios_config::jsonb -> 'enabled')::boolean WHERE id = qrCodeRecord.id;
UPDATE mobile_app SET store_info = qrCodeRecord.ios_config::jsonb - 'enabled' WHERE id = iosAppId; UPDATE mobile_app SET store_info = qrCodeRecord.ios_config::jsonb - 'enabled' WHERE id = iosAppId;
END IF; END IF;
END IF; END IF;
UPDATE qr_code_settings SET ios_enabled = (qrCodeRecord.ios_config::jsonb -> 'enabled')::boolean WHERE id = qrCodeRecord.id;
END LOOP; END LOOP;
ALTER TABLE qr_code_settings RENAME CONSTRAINT mobile_app_settings_tenant_id_unq_key TO qr_code_settings_tenant_id_unq_key; ALTER TABLE qr_code_settings RENAME CONSTRAINT mobile_app_settings_tenant_id_unq_key TO qr_code_settings_tenant_id_unq_key;
ALTER TABLE qr_code_settings RENAME CONSTRAINT mobile_app_settings_pkey TO qr_code_settings_pkey;
END IF; END IF;
ALTER TABLE qr_code_settings DROP COLUMN IF EXISTS android_config, DROP COLUMN IF EXISTS ios_config; ALTER TABLE qr_code_settings DROP COLUMN IF EXISTS android_config, DROP COLUMN IF EXISTS ios_config;
END; END;
$$; $$;
-- update constraint name
DO
$$
BEGIN
ALTER TABLE domain DROP CONSTRAINT IF EXISTS domain_unq_key;
IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'domain_name_key') THEN
ALTER TABLE domain ADD CONSTRAINT domain_name_key UNIQUE (name);
END IF;
END;
$$;
-- UPDATE RESOURCE JS_MODULE SUB TYPE START -- UPDATE RESOURCE JS_MODULE SUB TYPE START
UPDATE resource SET resource_sub_type = 'EXTENSION' WHERE resource_type = 'JS_MODULE' AND resource_sub_type IS NULL; UPDATE resource SET resource_sub_type = 'EXTENSION' WHERE resource_type = 'JS_MODULE' AND resource_sub_type IS NULL;

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

@ -173,7 +173,10 @@ public class DefaultTbContext implements TbContext {
if (!msg.isValid()) { if (!msg.isValid()) {
return; return;
} }
TbMsg tbMsg = msg.copyWithRuleChainId(ruleChainId); TbMsg tbMsg = msg.copy()
.ruleChainId(ruleChainId)
.resetRuleNodeId()
.build();
tbMsg.pushToStack(nodeCtx.getSelf().getRuleChainId(), nodeCtx.getSelf().getId()); tbMsg.pushToStack(nodeCtx.getSelf().getRuleChainId(), nodeCtx.getSelf().getId());
TopicPartitionInfo tpi = mainCtx.resolve(ServiceType.TB_RULE_ENGINE, getQueueName(), getTenantId(), tbMsg.getOriginator()); TopicPartitionInfo tpi = mainCtx.resolve(ServiceType.TB_RULE_ENGINE, getQueueName(), getTenantId(), tbMsg.getOriginator());
doEnqueue(tpi, tbMsg, new SimpleTbQueueCallback(md -> ack(msg), t -> tellFailure(msg, t))); doEnqueue(tpi, tbMsg, new SimpleTbQueueCallback(md -> ack(msg), t -> tellFailure(msg, t)));
@ -361,19 +364,28 @@ public class DefaultTbContext implements TbContext {
nodeCtx.setSelf(self); nodeCtx.setSelf(self);
} }
@Override
public TbMsg newMsg(String queueName, String type, EntityId originator, TbMsgMetaData metaData, String data) {
return newMsg(queueName, type, originator, null, metaData, data);
}
@Override @Override
public TbMsg newMsg(String queueName, String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data) { public TbMsg newMsg(String queueName, String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data) {
return TbMsg.newMsg(queueName, type, originator, customerId, metaData, data, nodeCtx.getSelf().getRuleChainId(), nodeCtx.getSelf().getId()); return TbMsg.newMsg()
.queueName(queueName)
.type(type)
.originator(originator)
.customerId(customerId)
.copyMetaData(metaData)
.data(data)
.ruleChainId(nodeCtx.getSelf().getRuleChainId())
.ruleNodeId(nodeCtx.getSelf().getId())
.build();
} }
@Override @Override
public TbMsg transformMsg(TbMsg origMsg, String type, EntityId originator, TbMsgMetaData metaData, String data) { public TbMsg transformMsg(TbMsg origMsg, String type, EntityId originator, TbMsgMetaData metaData, String data) {
return TbMsg.transformMsg(origMsg, type, originator, metaData, data); return origMsg.transform()
.type(type)
.originator(originator)
.metaData(metaData)
.data(data)
.build();
} }
@Override @Override
@ -383,22 +395,41 @@ public class DefaultTbContext implements TbContext {
@Override @Override
public TbMsg newMsg(String queueName, TbMsgType type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data) { public TbMsg newMsg(String queueName, TbMsgType type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data) {
return TbMsg.newMsg(queueName, type, originator, customerId, metaData, data, nodeCtx.getSelf().getRuleChainId(), nodeCtx.getSelf().getId()); return TbMsg.newMsg()
.queueName(queueName)
.type(type)
.originator(originator)
.customerId(customerId)
.copyMetaData(metaData)
.data(data)
.ruleChainId(nodeCtx.getSelf().getRuleChainId())
.ruleNodeId(nodeCtx.getSelf().getId())
.build();
} }
@Override @Override
public TbMsg transformMsg(TbMsg origMsg, TbMsgType type, EntityId originator, TbMsgMetaData metaData, String data) { public TbMsg transformMsg(TbMsg origMsg, TbMsgType type, EntityId originator, TbMsgMetaData metaData, String data) {
return TbMsg.transformMsg(origMsg, type, originator, metaData, data); return origMsg.transform()
.type(type)
.originator(originator)
.metaData(metaData)
.data(data)
.build();
} }
@Override @Override
public TbMsg transformMsg(TbMsg origMsg, TbMsgMetaData metaData, String data) { public TbMsg transformMsg(TbMsg origMsg, TbMsgMetaData metaData, String data) {
return TbMsg.transformMsg(origMsg, metaData, data); return origMsg.transform()
.metaData(metaData)
.data(data)
.build();
} }
@Override @Override
public TbMsg transformMsgOriginator(TbMsg origMsg, EntityId originator) { public TbMsg transformMsgOriginator(TbMsg origMsg, EntityId originator) {
return TbMsg.transformMsgOriginator(origMsg, originator); return origMsg.transform()
.originator(originator)
.build();
} }
@Override @Override
@ -497,7 +528,14 @@ public class DefaultTbContext implements TbContext {
defaultQueueName = profile.getDefaultQueueName(); defaultQueueName = profile.getDefaultQueueName();
defaultRuleChainId = profile.getDefaultRuleChainId(); defaultRuleChainId = profile.getDefaultRuleChainId();
} }
return TbMsg.newMsg(defaultQueueName, action, id, msgMetaData, msgData, defaultRuleChainId, null); return TbMsg.newMsg()
.queueName(defaultQueueName)
.type(action)
.originator(id)
.copyMetaData(msgMetaData)
.data(msgData)
.ruleChainId(defaultRuleChainId)
.build();
} }
public <E, I extends EntityId, K extends HasRuleEngineProfile> TbMsg entityActionMsg(E entity, I id, RuleNodeId ruleNodeId, TbMsgType actionMsgType, K profile) { public <E, I extends EntityId, K extends HasRuleEngineProfile> TbMsg entityActionMsg(E entity, I id, RuleNodeId ruleNodeId, TbMsgType actionMsgType, K profile) {
@ -515,7 +553,14 @@ public class DefaultTbContext implements TbContext {
defaultQueueName = profile.getDefaultQueueName(); defaultQueueName = profile.getDefaultQueueName();
defaultRuleChainId = profile.getDefaultRuleChainId(); defaultRuleChainId = profile.getDefaultRuleChainId();
} }
return TbMsg.newMsg(defaultQueueName, actionMsgType, id, msgMetaData, msgData, defaultRuleChainId, null); return TbMsg.newMsg()
.queueName(defaultQueueName)
.type(actionMsgType)
.originator(id)
.copyMetaData(msgMetaData)
.data(msgData)
.ruleChainId(defaultRuleChainId)
.build();
} }
@Override @Override

19
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java

@ -16,6 +16,7 @@
package org.thingsboard.server.actors.ruleChain; package org.thingsboard.server.actors.ruleChain;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.common.util.DebugModeUtil;
import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.TbActorCtx; import org.thingsboard.server.actors.TbActorCtx;
import org.thingsboard.server.actors.TbActorRef; import org.thingsboard.server.actors.TbActorRef;
@ -35,7 +36,6 @@ import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.common.util.DebugModeUtil;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.plugin.RuleNodeUpdatedMsg; import org.thingsboard.server.common.msg.plugin.RuleNodeUpdatedMsg;
@ -217,7 +217,10 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
RuleNodeCtx targetCtx; RuleNodeCtx targetCtx;
if (targetId == null) { if (targetId == null) {
targetCtx = firstNode; targetCtx = firstNode;
msg = msg.copyWithRuleChainId(entityId); msg = msg.copy()
.ruleChainId(entityId)
.resetRuleNodeId()
.build();
} else { } else {
targetCtx = nodeActors.get(targetId); targetCtx = nodeActors.get(targetId);
} }
@ -343,10 +346,18 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
private void putToQueue(TopicPartitionInfo tpi, TbMsg msg, TbQueueCallback callbackWrapper, EntityId target) { private void putToQueue(TopicPartitionInfo tpi, TbMsg msg, TbQueueCallback callbackWrapper, EntityId target) {
switch (target.getEntityType()) { switch (target.getEntityType()) {
case RULE_NODE: case RULE_NODE:
putToQueue(tpi, msg.copyWithRuleNodeId(entityId, new RuleNodeId(target.getId()), UUID.randomUUID()), callbackWrapper); putToQueue(tpi, msg.copy()
.id(UUID.randomUUID())
.ruleChainId(entityId)
.ruleNodeId(new RuleNodeId(target.getId()))
.build(), callbackWrapper);
break; break;
case RULE_CHAIN: case RULE_CHAIN:
putToQueue(tpi, msg.copyWithRuleChainId(new RuleChainId(target.getId()), UUID.randomUUID()), callbackWrapper); putToQueue(tpi, msg.copy()
.id(UUID.randomUUID())
.ruleChainId(new RuleChainId(target.getId()))
.resetRuleNodeId()
.build(), callbackWrapper);
break; break;
} }
} }

2
application/src/main/java/org/thingsboard/server/controller/AssetProfileController.java

@ -88,7 +88,7 @@ public class AssetProfileController extends BaseController {
AssetProfileId assetProfileId = new AssetProfileId(toUUID(strAssetProfileId)); AssetProfileId assetProfileId = new AssetProfileId(toUUID(strAssetProfileId));
var result = checkAssetProfileId(assetProfileId, Operation.READ); var result = checkAssetProfileId(assetProfileId, Operation.READ);
if (inlineImages) { if (inlineImages) {
imageService.inlineImage(result); result = imageService.inlineImage(result);
} }
return result; return result;
} }

1
application/src/main/java/org/thingsboard/server/controller/BaseController.java

@ -923,6 +923,7 @@ public abstract class BaseController {
UserCredentials userCredentials = userService.findUserCredentialsByUserId(user.getTenantId(), user.getId()); UserCredentials userCredentials = userService.findUserCredentialsByUserId(user.getTenantId(), user.getId());
info.put("userCredentialsEnabled", userCredentials.isEnabled()); info.put("userCredentialsEnabled", userCredentials.isEnabled());
info.put("userActivated", userCredentials.getActivateToken() == null);
info.put("lastLoginTs", userCredentials.getLastLoginTs()); info.put("lastLoginTs", userCredentials.getLastLoginTs());
} }

2
application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java

@ -96,7 +96,7 @@ public class DeviceProfileController extends BaseController {
DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId)); DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId));
var result = checkDeviceProfileId(deviceProfileId, Operation.READ); var result = checkDeviceProfileId(deviceProfileId, Operation.READ);
if (inlineImages) { if (inlineImages) {
imageService.inlineImage(result); result = imageService.inlineImage(result);
} }
return result; return result;
} }

2
application/src/main/java/org/thingsboard/server/controller/QrCodeSettingsController.java

@ -176,7 +176,7 @@ public class QrCodeSettingsController extends BaseController {
return ResponseEntity.status(HttpStatus.FOUND) return ResponseEntity.status(HttpStatus.FOUND)
.header("Location", googlePlayLink) .header("Location", googlePlayLink)
.build(); .build();
} else if (userAgent.contains("iPhone") || userAgent.contains("iPad") && qrCodeSettings.isIosEnabled()) { } else if ((userAgent.contains("iPhone") || userAgent.contains("iPad")) && qrCodeSettings.isIosEnabled()) {
String appStoreLink = qrCodeSettings.getAppStoreLink(); String appStoreLink = qrCodeSettings.getAppStoreLink();
return ResponseEntity.status(HttpStatus.FOUND) return ResponseEntity.status(HttpStatus.FOUND)
.header("Location", appStoreLink) .header("Location", appStoreLink)

7
application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java

@ -239,7 +239,12 @@ public class RpcV2Controller extends AbstractRpcController {
rpcService.deleteRpc(getTenantId(), rpcId); rpcService.deleteRpc(getTenantId(), rpcId);
rpc.setStatus(RpcStatus.DELETED); rpc.setStatus(RpcStatus.DELETED);
TbMsg msg = TbMsg.newMsg(TbMsgType.RPC_DELETED, rpc.getDeviceId(), TbMsgMetaData.EMPTY, JacksonUtil.toString(rpc)); TbMsg msg = TbMsg.newMsg()
.type(TbMsgType.RPC_DELETED)
.originator(rpc.getDeviceId())
.copyMetaData(TbMsgMetaData.EMPTY)
.data(JacksonUtil.toString(rpc))
.build();
tbClusterService.pushMsgToRuleEngine(getTenantId(), rpc.getDeviceId(), msg, null); tbClusterService.pushMsgToRuleEngine(getTenantId(), rpc.getDeviceId(), msg, null);
} }
} }

7
application/src/main/java/org/thingsboard/server/controller/RuleChainController.java

@ -384,7 +384,12 @@ public class RuleChainController extends BaseController {
} }
engine = new RuleNodeTbelScriptEngine(getTenantId(), tbelInvokeService, script, argNames); engine = new RuleNodeTbelScriptEngine(getTenantId(), tbelInvokeService, script, argNames);
} }
TbMsg inMsg = TbMsg.newMsg(msgType, null, new TbMsgMetaData(metadata), TbMsgDataType.JSON, data); TbMsg inMsg = TbMsg.newMsg()
.type(msgType)
.copyMetaData(new TbMsgMetaData(metadata))
.dataType(TbMsgDataType.JSON)
.data(data)
.build();
switch (scriptType) { switch (scriptType) {
case "update": case "update":
output = msgToOutput(engine.executeUpdateAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS)); output = msgToOutput(engine.executeUpdateAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS));

9
application/src/main/java/org/thingsboard/server/controller/RuleEngineController.java

@ -169,7 +169,14 @@ public class RuleEngineController extends BaseController {
metaData.put("serviceId", serviceInfoProvider.getServiceId()); metaData.put("serviceId", serviceInfoProvider.getServiceId());
metaData.put("requestUUID", requestId.toString()); metaData.put("requestUUID", requestId.toString());
metaData.put("expirationTime", Long.toString(expTime)); metaData.put("expirationTime", Long.toString(expTime));
TbMsg msg = TbMsg.newMsg(queueName, TbMsgType.REST_API_REQUEST, entityId, currentUser.getCustomerId(), new TbMsgMetaData(metaData), requestBody); TbMsg msg = TbMsg.newMsg()
.queueName(queueName)
.type(TbMsgType.REST_API_REQUEST)
.originator(entityId)
.customerId(currentUser.getCustomerId())
.copyMetaData(new TbMsgMetaData(metaData))
.data(requestBody)
.build();
ruleEngineCallService.processRestApiCallToRuleEngine(currentUser.getTenantId(), requestId, msg, queueName != null, ruleEngineCallService.processRestApiCallToRuleEngine(currentUser.getTenantId(), requestId, msg, queueName != null,
reply -> reply(new LocalRequestMetaData(msg, currentUser, result), reply)); reply -> reply(new LocalRequestMetaData(msg, currentUser, result), reply));
} }

163
application/src/main/java/org/thingsboard/server/controller/TelemetryController.java

@ -47,6 +47,10 @@ import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult; import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.rule.engine.api.AttributesDeleteRequest;
import org.thingsboard.rule.engine.api.AttributesSaveRequest;
import org.thingsboard.rule.engine.api.TimeseriesDeleteRequest;
import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
import org.thingsboard.server.common.adaptor.JsonConverter; import org.thingsboard.server.common.adaptor.JsonConverter;
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;
@ -399,7 +403,7 @@ public class TelemetryController extends BaseController {
public DeferredResult<ResponseEntity> saveEntityAttributesV2( public DeferredResult<ResponseEntity> saveEntityAttributesV2(
@Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType, @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType,
@Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
@Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE"}, requiredMode = Schema.RequiredMode.REQUIRED)) @PathVariable("scope")AttributeScope scope, @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE"}, requiredMode = Schema.RequiredMode.REQUIRED)) @PathVariable("scope") AttributeScope scope,
@io.swagger.v3.oas.annotations.parameters.RequestBody(description = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException { @io.swagger.v3.oas.annotations.parameters.RequestBody(description = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
return saveAttributes(getTenantId(), entityId, scope, request); return saveAttributes(getTenantId(), entityId, scope, request);
@ -423,8 +427,8 @@ public class TelemetryController extends BaseController {
public DeferredResult<ResponseEntity> saveEntityTelemetry( public DeferredResult<ResponseEntity> saveEntityTelemetry(
@Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType, @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType,
@Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
@Parameter(description = TELEMETRY_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = "ANY")) @PathVariable("scope")String scope, @Parameter(description = TELEMETRY_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = "ANY")) @PathVariable("scope") String scope,
@io.swagger.v3.oas.annotations.parameters.RequestBody(description = TELEMETRY_JSON_REQUEST_DESCRIPTION, required = true)@RequestBody String requestBody) throws ThingsboardException { @io.swagger.v3.oas.annotations.parameters.RequestBody(description = TELEMETRY_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody String requestBody) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
return saveTelemetry(getTenantId(), entityId, requestBody, 0L); return saveTelemetry(getTenantId(), entityId, requestBody, 0L);
} }
@ -447,9 +451,9 @@ public class TelemetryController extends BaseController {
public DeferredResult<ResponseEntity> saveEntityTelemetryWithTTL( public DeferredResult<ResponseEntity> saveEntityTelemetryWithTTL(
@Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType, @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType,
@Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
@Parameter(description = TELEMETRY_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = "ANY")) @PathVariable("scope")String scope, @Parameter(description = TELEMETRY_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = "ANY")) @PathVariable("scope") String scope,
@Parameter(description = "A long value representing TTL (Time to Live) parameter.", required = true)@PathVariable("ttl")Long ttl, @Parameter(description = "A long value representing TTL (Time to Live) parameter.", required = true) @PathVariable("ttl") Long ttl,
@io.swagger.v3.oas.annotations.parameters.RequestBody(description = TELEMETRY_JSON_REQUEST_DESCRIPTION, required = true)@RequestBody String requestBody) throws ThingsboardException { @io.swagger.v3.oas.annotations.parameters.RequestBody(description = TELEMETRY_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody String requestBody) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
return saveTelemetry(getTenantId(), entityId, requestBody, ttl); return saveTelemetry(getTenantId(), entityId, requestBody, ttl);
} }
@ -518,19 +522,25 @@ public class TelemetryController extends BaseController {
for (String key : keys) { for (String key : keys) {
deleteTsKvQueries.add(new BaseDeleteTsKvQuery(key, deleteFromTs, deleteToTs, rewriteLatestIfDeleted, deleteLatest)); deleteTsKvQueries.add(new BaseDeleteTsKvQuery(key, deleteFromTs, deleteToTs, rewriteLatestIfDeleted, deleteLatest));
} }
tsSubService.deleteTimeseriesAndNotify(tenantId, entityId, keys, deleteTsKvQueries, new FutureCallback<>() { tsSubService.deleteTimeseries(TimeseriesDeleteRequest.builder()
@Override .tenantId(tenantId)
public void onSuccess(@Nullable Void tmp) { .entityId(entityId)
logTimeseriesDeleted(user, entityId, keys, deleteFromTs, deleteToTs, null); .keys(keys)
result.setResult(new ResponseEntity<>(HttpStatus.OK)); .deleteHistoryQueries(deleteTsKvQueries)
} .callback(new FutureCallback<>() {
@Override
@Override public void onSuccess(@Nullable List<String> tmp) {
public void onFailure(Throwable t) { logTimeseriesDeleted(user, entityId, keys, deleteFromTs, deleteToTs, null);
logTimeseriesDeleted(user, entityId, keys, deleteFromTs, deleteToTs, t); result.setResult(new ResponseEntity<>(HttpStatus.OK));
result.setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR)); }
}
}); @Override
public void onFailure(Throwable t) {
logTimeseriesDeleted(user, entityId, keys, deleteFromTs, deleteToTs, t);
result.setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
}
})
.build());
}); });
} }
@ -550,8 +560,8 @@ public class TelemetryController extends BaseController {
@ResponseBody @ResponseBody
public DeferredResult<ResponseEntity> deleteDeviceAttributes( public DeferredResult<ResponseEntity> deleteDeviceAttributes(
@Parameter(description = DEVICE_ID_PARAM_DESCRIPTION, required = true) @PathVariable(DEVICE_ID) String deviceIdStr, @Parameter(description = DEVICE_ID_PARAM_DESCRIPTION, required = true) @PathVariable(DEVICE_ID) String deviceIdStr,
@Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE", "CLIENT_SCOPE"}, requiredMode = Schema.RequiredMode.REQUIRED)) @PathVariable("scope")AttributeScope scope, @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE", "CLIENT_SCOPE"}, requiredMode = Schema.RequiredMode.REQUIRED)) @PathVariable("scope") AttributeScope scope,
@Parameter(description = ATTRIBUTES_KEYS_DESCRIPTION, required = true)@RequestParam(name = "keys")String keysStr) throws ThingsboardException { @Parameter(description = ATTRIBUTES_KEYS_DESCRIPTION, required = true) @RequestParam(name = "keys") String keysStr) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE, deviceIdStr); EntityId entityId = EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE, deviceIdStr);
return deleteAttributes(entityId, scope, keysStr); return deleteAttributes(entityId, scope, keysStr);
} }
@ -573,8 +583,8 @@ public class TelemetryController extends BaseController {
public DeferredResult<ResponseEntity> deleteEntityAttributes( public DeferredResult<ResponseEntity> deleteEntityAttributes(
@Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType, @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType,
@Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
@Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE", "CLIENT_SCOPE"})) @PathVariable("scope")AttributeScope scope, @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE", "CLIENT_SCOPE"})) @PathVariable("scope") AttributeScope scope,
@Parameter(description = ATTRIBUTES_KEYS_DESCRIPTION, required = true)@RequestParam(name = "keys")String keysStr) throws ThingsboardException { @Parameter(description = ATTRIBUTES_KEYS_DESCRIPTION, required = true) @RequestParam(name = "keys") String keysStr) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
return deleteAttributes(entityId, scope, keysStr); return deleteAttributes(entityId, scope, keysStr);
} }
@ -587,24 +597,30 @@ public class TelemetryController extends BaseController {
SecurityUser user = getCurrentUser(); SecurityUser user = getCurrentUser();
return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.WRITE_ATTRIBUTES, entityIdSrc, (result, tenantId, entityId) -> { return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.WRITE_ATTRIBUTES, entityIdSrc, (result, tenantId, entityId) -> {
tsSubService.deleteAndNotify(tenantId, entityId, scope.name(), keys, new FutureCallback<Void>() { tsSubService.deleteAttributes(AttributesDeleteRequest.builder()
@Override .tenantId(tenantId)
public void onSuccess(@Nullable Void tmp) { .entityId(entityId)
logAttributesDeleted(user, entityId, scope, keys, null); .scope(scope)
if (entityIdSrc.getEntityType().equals(EntityType.DEVICE)) { .keys(keys)
DeviceId deviceId = new DeviceId(entityId.getId()); .callback(new FutureCallback<>() {
tbClusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete( @Override
user.getTenantId(), deviceId, scope.name(), keys), null); public void onSuccess(@Nullable Void tmp) {
} logAttributesDeleted(user, entityId, scope, keys, null);
result.setResult(new ResponseEntity<>(HttpStatus.OK)); if (entityIdSrc.getEntityType().equals(EntityType.DEVICE)) {
} DeviceId deviceId = new DeviceId(entityId.getId());
tbClusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete(
@Override user.getTenantId(), deviceId, scope.name(), keys), null);
public void onFailure(Throwable t) { }
logAttributesDeleted(user, entityId, scope, keys, t); result.setResult(new ResponseEntity<>(HttpStatus.OK));
result.setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR)); }
}
}); @Override
public void onFailure(Throwable t) {
logAttributesDeleted(user, entityId, scope, keys, t);
result.setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
}
})
.build());
}); });
} }
@ -624,19 +640,25 @@ public class TelemetryController extends BaseController {
} }
SecurityUser user = getCurrentUser(); SecurityUser user = getCurrentUser();
return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.WRITE_ATTRIBUTES, entityIdSrc, (result, tenantId, entityId) -> { return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.WRITE_ATTRIBUTES, entityIdSrc, (result, tenantId, entityId) -> {
tsSubService.saveAndNotify(tenantId, entityId, scope, attributes, new FutureCallback<Void>() { tsSubService.saveAttributes(AttributesSaveRequest.builder()
@Override .tenantId(tenantId)
public void onSuccess(@Nullable Void tmp) { .entityId(entityId)
logAttributesUpdated(user, entityId, scope, attributes, null); .scope(scope)
result.setResult(new ResponseEntity(HttpStatus.OK)); .entries(attributes)
} .callback(new FutureCallback<>() {
@Override
@Override public void onSuccess(@Nullable Void tmp) {
public void onFailure(Throwable t) { logAttributesUpdated(user, entityId, scope, attributes, null);
logAttributesUpdated(user, entityId, scope, attributes, t); result.setResult(new ResponseEntity(HttpStatus.OK));
AccessValidator.handleError(t, result, HttpStatus.INTERNAL_SERVER_ERROR); }
}
}); @Override
public void onFailure(Throwable t) {
logAttributesUpdated(user, entityId, scope, attributes, t);
AccessValidator.handleError(t, result, HttpStatus.INTERNAL_SERVER_ERROR);
}
})
.build());
}); });
} else { } else {
return getImmediateDeferredResult("Request is not a JSON object", HttpStatus.BAD_REQUEST); return getImmediateDeferredResult("Request is not a JSON object", HttpStatus.BAD_REQUEST);
@ -672,19 +694,26 @@ public class TelemetryController extends BaseController {
TenantProfile tenantProfile = tenantProfileCache.get(tenantId); TenantProfile tenantProfile = tenantProfileCache.get(tenantId);
tenantTtl = TimeUnit.DAYS.toSeconds(((DefaultTenantProfileConfiguration) tenantProfile.getProfileData().getConfiguration()).getDefaultStorageTtlDays()); tenantTtl = TimeUnit.DAYS.toSeconds(((DefaultTenantProfileConfiguration) tenantProfile.getProfileData().getConfiguration()).getDefaultStorageTtlDays());
} }
tsSubService.saveAndNotify(tenantId, user.getCustomerId(), entityId, entries, tenantTtl, new FutureCallback<Void>() { tsSubService.saveTimeseries(TimeseriesSaveRequest.builder()
@Override .tenantId(tenantId)
public void onSuccess(@Nullable Void tmp) { .customerId(user.getCustomerId())
logTelemetryUpdated(user, entityId, entries, null); .entityId(entityId)
result.setResult(new ResponseEntity(HttpStatus.OK)); .entries(entries)
} .ttl(tenantTtl)
.callback(new FutureCallback<Void>() {
@Override @Override
public void onFailure(Throwable t) { public void onSuccess(@Nullable Void tmp) {
logTelemetryUpdated(user, entityId, entries, t); logTelemetryUpdated(user, entityId, entries, null);
AccessValidator.handleError(t, result, HttpStatus.INTERNAL_SERVER_ERROR); result.setResult(new ResponseEntity(HttpStatus.OK));
} }
});
@Override
public void onFailure(Throwable t) {
logTelemetryUpdated(user, entityId, entries, t);
AccessValidator.handleError(t, result, HttpStatus.INTERNAL_SERVER_ERROR);
}
})
.build());
}); });
} }

2
application/src/main/java/org/thingsboard/server/controller/WidgetsBundleController.java

@ -90,7 +90,7 @@ public class WidgetsBundleController extends BaseController {
WidgetsBundleId widgetsBundleId = new WidgetsBundleId(toUUID(strWidgetsBundleId)); WidgetsBundleId widgetsBundleId = new WidgetsBundleId(toUUID(strWidgetsBundleId));
var result = checkWidgetsBundleId(widgetsBundleId, Operation.READ); var result = checkWidgetsBundleId(widgetsBundleId, Operation.READ);
if (inlineImages) { if (inlineImages) {
imageService.inlineImage(result); result = imageService.inlineImage(result);
} }
return result; return result;
} }

9
application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java

@ -172,7 +172,14 @@ public class EntityActionService {
if (tenantId != null && !tenantId.isSysTenantId()) { if (tenantId != null && !tenantId.isSysTenantId()) {
processNotificationRules(tenantId, entityId, entity, actionType, user, additionalInfo); processNotificationRules(tenantId, entityId, entity, actionType, user, additionalInfo);
} }
TbMsg tbMsg = TbMsg.newMsg(msgType.get(), entityId, customerId, metaData, TbMsgDataType.JSON, JacksonUtil.toString(entityNode)); TbMsg tbMsg = TbMsg.newMsg()
.type(msgType.get())
.originator(entityId)
.customerId(customerId)
.copyMetaData(metaData)
.dataType(TbMsgDataType.JSON)
.data(JacksonUtil.toString(entityNode))
.build();
tbClusterService.pushMsgToRuleEngine(tenantId, entityId, tbMsg, null); tbClusterService.pushMsgToRuleEngine(tenantId, entityId, tbMsg, null);
} catch (Exception e) { } catch (Exception e) {
log.warn("[{}] Failed to push entity action to rule engine: {}", entityId, actionType, e); log.warn("[{}] Failed to push entity action to rule engine: {}", entityId, actionType, e);

33
application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java

@ -27,6 +27,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
import org.thingsboard.server.common.data.ApiFeature; import org.thingsboard.server.common.data.ApiFeature;
import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.ApiUsageRecordState; import org.thingsboard.server.common.data.ApiUsageRecordState;
@ -91,9 +92,9 @@ import java.util.stream.Collectors;
public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService<EntityId> implements TbApiUsageStateService { public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService<EntityId> implements TbApiUsageStateService {
public static final String HOURLY = "Hourly"; public static final String HOURLY = "Hourly";
public static final FutureCallback<Integer> VOID_CALLBACK = new FutureCallback<Integer>() { public static final FutureCallback<Void> VOID_CALLBACK = new FutureCallback<Void>() {
@Override @Override
public void onSuccess(@Nullable Integer result) { public void onSuccess(@Nullable Void result) {
} }
@Override @Override
@ -214,7 +215,12 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
updateLock.unlock(); updateLock.unlock();
} }
log.trace("[{}][{}] Saving new stats: {}", tenantId, ownerId, updatedEntries); log.trace("[{}][{}] Saving new stats: {}", tenantId, ownerId, updatedEntries);
tsWsService.saveAndNotifyInternal(tenantId, usageState.getApiUsageState().getId(), updatedEntries, VOID_CALLBACK); tsWsService.saveTimeseriesInternal(TimeseriesSaveRequest.builder()
.tenantId(tenantId)
.entityId(usageState.getApiUsageState().getId())
.entries(updatedEntries)
.callback(VOID_CALLBACK)
.build());
if (!result.isEmpty()) { if (!result.isEmpty()) {
persistAndNotify(usageState, result); persistAndNotify(usageState, result);
} }
@ -321,7 +327,12 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
} }
} }
if (!profileThresholds.isEmpty()) { if (!profileThresholds.isEmpty()) {
tsWsService.saveAndNotifyInternal(tenantId, id, profileThresholds, VOID_CALLBACK); tsWsService.saveTimeseriesInternal(TimeseriesSaveRequest.builder()
.tenantId(tenantId)
.entityId(id)
.entries(profileThresholds)
.callback(VOID_CALLBACK)
.build());
} }
} }
@ -348,7 +359,12 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
long ts = System.currentTimeMillis(); long ts = System.currentTimeMillis();
List<TsKvEntry> stateTelemetry = new ArrayList<>(); List<TsKvEntry> stateTelemetry = new ArrayList<>();
result.forEach((apiFeature, aState) -> stateTelemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(apiFeature.getApiStateKey(), aState.name())))); result.forEach((apiFeature, aState) -> stateTelemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(apiFeature.getApiStateKey(), aState.name()))));
tsWsService.saveAndNotifyInternal(state.getTenantId(), state.getApiUsageState().getId(), stateTelemetry, VOID_CALLBACK); tsWsService.saveTimeseriesInternal(TimeseriesSaveRequest.builder()
.tenantId(state.getTenantId())
.entityId(state.getApiUsageState().getId())
.entries(stateTelemetry)
.callback(VOID_CALLBACK)
.build());
if (state.getEntityType() == EntityType.TENANT && !state.getEntityId().equals(TenantId.SYS_TENANT_ID)) { if (state.getEntityType() == EntityType.TENANT && !state.getEntityId().equals(TenantId.SYS_TENANT_ID)) {
String email = tenantService.findTenantById(state.getTenantId()).getEmail(); String email = tenantService.findTenantById(state.getTenantId()).getEmail();
@ -436,7 +452,12 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
.map(key -> new BasicTsKvEntry(state.getCurrentCycleTs(), new LongDataEntry(key.getApiCountKey(), 0L))) .map(key -> new BasicTsKvEntry(state.getCurrentCycleTs(), new LongDataEntry(key.getApiCountKey(), 0L)))
.collect(Collectors.toList()); .collect(Collectors.toList());
tsWsService.saveAndNotifyInternal(state.getTenantId(), state.getApiUsageState().getId(), counts, VOID_CALLBACK); tsWsService.saveTimeseriesInternal(TimeseriesSaveRequest.builder()
.tenantId(state.getTenantId())
.entityId(state.getApiUsageState().getId())
.entries(counts)
.callback(VOID_CALLBACK)
.build());
} }
BaseApiUsageState getOrFetchState(TenantId tenantId, EntityId ownerId) { BaseApiUsageState getOrFetchState(TenantId tenantId, EntityId ownerId) {

36
application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java

@ -28,6 +28,8 @@ import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager; import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.AttributesDeleteRequest;
import org.thingsboard.rule.engine.api.AttributesSaveRequest;
import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
@ -37,7 +39,6 @@ import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.customer.CustomerService;
@ -178,11 +179,12 @@ public class ClaimDevicesServiceImpl implements ClaimDevicesService {
return Futures.immediateFuture(new ReclaimResult(unassignedCustomer)); return Futures.immediateFuture(new ReclaimResult(unassignedCustomer));
} }
SettableFuture<ReclaimResult> result = SettableFuture.create(); SettableFuture<ReclaimResult> result = SettableFuture.create();
telemetryService.saveAndNotify( telemetryService.saveAttributes(AttributesSaveRequest.builder()
tenantId, savedDevice.getId(), AttributeScope.SERVER_SCOPE, List.of( .tenantId(tenantId)
new BaseAttributeKvEntry(new BooleanDataEntry(CLAIM_ATTRIBUTE_NAME, true), System.currentTimeMillis()) .entityId(savedDevice.getId())
), .scope(AttributeScope.SERVER_SCOPE)
new FutureCallback<>() { .entry(new BooleanDataEntry(CLAIM_ATTRIBUTE_NAME, true))
.callback(new FutureCallback<>() {
@Override @Override
public void onSuccess(@Nullable Void tmp) { public void onSuccess(@Nullable Void tmp) {
result.set(new ReclaimResult(unassignedCustomer)); result.set(new ReclaimResult(unassignedCustomer));
@ -192,7 +194,8 @@ public class ClaimDevicesServiceImpl implements ClaimDevicesService {
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
result.setException(t); result.setException(t);
} }
}); })
.build());
return result; return result;
} }
cacheEviction(device.getId()); cacheEviction(device.getId());
@ -221,18 +224,13 @@ public class ClaimDevicesServiceImpl implements ClaimDevicesService {
cache.evict(data.getKey()); cache.evict(data.getKey());
} }
SettableFuture<Void> result = SettableFuture.create(); SettableFuture<Void> result = SettableFuture.create();
telemetryService.deleteAndNotify(device.getTenantId(), telemetryService.deleteAttributes(AttributesDeleteRequest.builder()
device.getId(), AttributeScope.SERVER_SCOPE, Arrays.asList(CLAIM_ATTRIBUTE_NAME, CLAIM_DATA_ATTRIBUTE_NAME), new FutureCallback<>() { .tenantId(device.getTenantId())
@Override .entityId(device.getId())
public void onSuccess(@Nullable Void tmp) { .scope(AttributeScope.SERVER_SCOPE)
result.set(tmp); .keys(Arrays.asList(CLAIM_ATTRIBUTE_NAME, CLAIM_DATA_ATTRIBUTE_NAME))
} .future(result)
.build());
@Override
public void onFailure(Throwable t) {
result.setException(t);
}
});
return result; return result;
} }

16
application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java

@ -253,7 +253,13 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService {
private void pushProvisionEventToRuleEngine(ProvisionRequest request, Device device, TbMsgType type) { private void pushProvisionEventToRuleEngine(ProvisionRequest request, Device device, TbMsgType type) {
try { try {
JsonNode entityNode = JacksonUtil.valueToTree(request); JsonNode entityNode = JacksonUtil.valueToTree(request);
TbMsg msg = TbMsg.newMsg(type, device.getId(), device.getCustomerId(), createTbMsgMetaData(device), JacksonUtil.toString(entityNode)); TbMsg msg = TbMsg.newMsg()
.type(type)
.originator(device.getId())
.customerId(device.getCustomerId())
.copyMetaData(createTbMsgMetaData(device))
.data(JacksonUtil.toString(entityNode))
.build();
sendToRuleEngine(device.getTenantId(), msg, null); sendToRuleEngine(device.getTenantId(), msg, null);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
log.warn("[{}] Failed to push device action to rule engine: {}", device.getId(), type, e); log.warn("[{}] Failed to push device action to rule engine: {}", device.getId(), type, e);
@ -263,7 +269,13 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService {
private void pushDeviceCreatedEventToRuleEngine(Device device) { private void pushDeviceCreatedEventToRuleEngine(Device device) {
try { try {
ObjectNode entityNode = JacksonUtil.OBJECT_MAPPER.valueToTree(device); ObjectNode entityNode = JacksonUtil.OBJECT_MAPPER.valueToTree(device);
TbMsg msg = TbMsg.newMsg(TbMsgType.ENTITY_CREATED, device.getId(), device.getCustomerId(), createTbMsgMetaData(device), JacksonUtil.OBJECT_MAPPER.writeValueAsString(entityNode)); TbMsg msg = TbMsg.newMsg()
.type(TbMsgType.ENTITY_CREATED)
.originator(device.getId())
.customerId(device.getCustomerId())
.copyMetaData(createTbMsgMetaData(device))
.data(JacksonUtil.OBJECT_MAPPER.writeValueAsString(entityNode))
.build();
sendToRuleEngine(device.getTenantId(), msg, null); sendToRuleEngine(device.getTenantId(), msg, null);
} catch (JsonProcessingException | IllegalArgumentException e) { } catch (JsonProcessingException | IllegalArgumentException e) {
log.warn("[{}] Failed to push device action to rule engine: {}", device.getId(), TbMsgType.ENTITY_CREATED.name(), e); log.warn("[{}] Failed to push device action to rule engine: {}", device.getId(), TbMsgType.ENTITY_CREATED.name(), e);

84
application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java

@ -32,6 +32,8 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.rule.engine.api.AttributesSaveRequest;
import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
import org.thingsboard.server.cache.TbTransactionalCache; import org.thingsboard.server.cache.TbTransactionalCache;
import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.AttributeScope;
@ -41,7 +43,6 @@ 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.id.EdgeId; import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.msg.TbMsgType;
@ -68,7 +69,6 @@ import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
@ -413,23 +413,27 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i
if (Boolean.TRUE.equals(sessionNewEvents.get(edgeId))) { if (Boolean.TRUE.equals(sessionNewEvents.get(edgeId))) {
log.trace("[{}][{}] Set session new events flag to false", tenantId, edgeId.getId()); log.trace("[{}][{}] Set session new events flag to false", tenantId, edgeId.getId());
sessionNewEvents.put(edgeId, false); sessionNewEvents.put(edgeId, false);
processEdgeEventMigrationIfNeeded(session, edgeId);
session.processHighPriorityEvents(); session.processHighPriorityEvents();
Futures.addCallback(session.processEdgeEvents(), new FutureCallback<>() { processEdgeEventMigrationIfNeeded(session, edgeId);
@Override if (Boolean.TRUE.equals(edgeEventsMigrationProcessed.get(edgeId))) {
public void onSuccess(Boolean newEventsAdded) { Futures.addCallback(session.processEdgeEvents(), new FutureCallback<>() {
if (Boolean.TRUE.equals(newEventsAdded)) { @Override
sessionNewEvents.put(edgeId, true); public void onSuccess(Boolean newEventsAdded) {
if (Boolean.TRUE.equals(newEventsAdded)) {
sessionNewEvents.put(edgeId, true);
}
scheduleEdgeEventsCheck(session);
} }
scheduleEdgeEventsCheck(session);
} @Override
public void onFailure(Throwable t) {
@Override log.warn("[{}] Failed to process edge events for edge [{}]!", tenantId, session.getEdge().getId().getId(), t);
public void onFailure(Throwable t) { scheduleEdgeEventsCheck(session);
log.warn("[{}] Failed to process edge events for edge [{}]!", tenantId, session.getEdge().getId().getId(), t); }
scheduleEdgeEventsCheck(session); }, ctx.getGrpcCallbackExecutorService());
} } else {
}, ctx.getGrpcCallbackExecutorService()); scheduleEdgeEventsCheck(session);
}
} else { } else {
scheduleEdgeEventsCheck(session); scheduleEdgeEventsCheck(session);
} }
@ -457,8 +461,6 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i
scheduleEdgeEventsCheck(session); scheduleEdgeEventsCheck(session);
} else if (Boolean.FALSE.equals(eventsExist)) { } else if (Boolean.FALSE.equals(eventsExist)) {
edgeEventsMigrationProcessed.put(edgeId, true); edgeEventsMigrationProcessed.put(edgeId, true);
} else {
scheduleEdgeEventsCheck(session);
} }
} }
} }
@ -503,24 +505,40 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i
private void save(TenantId tenantId, EdgeId edgeId, String key, long value) { private void save(TenantId tenantId, EdgeId edgeId, String key, long value) {
log.debug("[{}][{}] Updating long edge telemetry [{}] [{}]", tenantId, edgeId, key, value); log.debug("[{}][{}] Updating long edge telemetry [{}] [{}]", tenantId, edgeId, key, value);
if (persistToTelemetry) { if (persistToTelemetry) {
tsSubService.saveAndNotify( tsSubService.saveTimeseries(TimeseriesSaveRequest.builder()
tenantId, edgeId, .tenantId(tenantId)
Collections.singletonList(new BasicTsKvEntry(System.currentTimeMillis(), new LongDataEntry(key, value))), .entityId(edgeId)
new AttributeSaveCallback(tenantId, edgeId, key, value)); .entry(new LongDataEntry(key, value))
.callback(new AttributeSaveCallback(tenantId, edgeId, key, value))
.build());
} else { } else {
tsSubService.saveAttrAndNotify(tenantId, edgeId, AttributeScope.SERVER_SCOPE, key, value, new AttributeSaveCallback(tenantId, edgeId, key, value)); tsSubService.saveAttributes(AttributesSaveRequest.builder()
.tenantId(tenantId)
.entityId(edgeId)
.scope(AttributeScope.SERVER_SCOPE)
.entry(new LongDataEntry(key, value))
.callback(new AttributeSaveCallback(tenantId, edgeId, key, value))
.build());
} }
} }
private void save(TenantId tenantId, EdgeId edgeId, String key, boolean value) { private void save(TenantId tenantId, EdgeId edgeId, String key, boolean value) {
log.debug("[{}][{}] Updating boolean edge telemetry [{}] [{}]", tenantId, edgeId, key, value); log.debug("[{}][{}] Updating boolean edge telemetry [{}] [{}]", tenantId, edgeId, key, value);
if (persistToTelemetry) { if (persistToTelemetry) {
tsSubService.saveAndNotify( tsSubService.saveTimeseries(TimeseriesSaveRequest.builder()
tenantId, edgeId, .tenantId(tenantId)
Collections.singletonList(new BasicTsKvEntry(System.currentTimeMillis(), new BooleanDataEntry(key, value))), .entityId(edgeId)
new AttributeSaveCallback(tenantId, edgeId, key, value)); .entry(new BooleanDataEntry(key, value))
.callback(new AttributeSaveCallback(tenantId, edgeId, key, value))
.build());
} else { } else {
tsSubService.saveAttrAndNotify(tenantId, edgeId, AttributeScope.SERVER_SCOPE, key, value, new AttributeSaveCallback(tenantId, edgeId, key, value)); tsSubService.saveAttributes(AttributesSaveRequest.builder()
.tenantId(tenantId)
.entityId(edgeId)
.scope(AttributeScope.SERVER_SCOPE)
.entry(new BooleanDataEntry(key, value))
.callback(new AttributeSaveCallback(tenantId, edgeId, key, value))
.build());
} }
} }
@ -575,7 +593,13 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i
md.putValue("edgeName", edge.getName()); md.putValue("edgeName", edge.getName());
md.putValue("edgeType", edge.getType()); md.putValue("edgeType", edge.getType());
} }
TbMsg tbMsg = TbMsg.newMsg(msgType, edgeId, md, TbMsgDataType.JSON, data); TbMsg tbMsg = TbMsg.newMsg()
.type(msgType)
.originator(edgeId)
.copyMetaData(md)
.dataType(TbMsgDataType.JSON)
.data(data)
.build();
clusterService.pushMsgToRuleEngine(tenantId, edgeId, tbMsg, null); clusterService.pushMsgToRuleEngine(tenantId, edgeId, tbMsg, null);
} catch (Exception e) { } catch (Exception e) {
log.warn("[{}][{}] Failed to push {}", tenantId, edge.getId(), msgType, e); log.warn("[{}][{}] Failed to push {}", tenantId, edge.getId(), msgType, e);

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

@ -110,7 +110,7 @@ public abstract class EdgeGrpcSession implements Closeable {
private static final String QUEUE_START_TS_ATTR_KEY = "queueStartTs"; private static final String QUEUE_START_TS_ATTR_KEY = "queueStartTs";
private static final String QUEUE_START_SEQ_ID_ATTR_KEY = "queueStartSeqId"; private static final String QUEUE_START_SEQ_ID_ATTR_KEY = "queueStartSeqId";
private static final int MAX_DOWNLINK_ATTEMPTS = 10; private static final int MAX_DOWNLINK_ATTEMPTS = 3;
private static final String RATE_LIMIT_REACHED = "Rate limit reached"; private static final String RATE_LIMIT_REACHED = "Rate limit reached";
protected static final ConcurrentLinkedQueue<EdgeEvent> highPriorityQueue = new ConcurrentLinkedQueue<>(); protected static final ConcurrentLinkedQueue<EdgeEvent> highPriorityQueue = new ConcurrentLinkedQueue<>();
@ -135,7 +135,7 @@ public abstract class EdgeGrpcSession implements Closeable {
private StreamObserver<RequestMsg> inputStream; private StreamObserver<RequestMsg> inputStream;
private StreamObserver<ResponseMsg> outputStream; private StreamObserver<ResponseMsg> outputStream;
private boolean connected; private volatile boolean connected;
private volatile boolean syncCompleted; private volatile boolean syncCompleted;
private EdgeVersion edgeVersion; private EdgeVersion edgeVersion;
@ -536,13 +536,15 @@ public abstract class EdgeGrpcSession implements Closeable {
public void processHighPriorityEvents() { public void processHighPriorityEvents() {
try { try {
List<EdgeEvent> highPriorityEvents = new ArrayList<>(); if (isConnected() && isSyncCompleted()) {
EdgeEvent event; List<EdgeEvent> highPriorityEvents = new ArrayList<>();
while ((event = highPriorityQueue.poll()) != null) { EdgeEvent event;
highPriorityEvents.add(event); while ((event = highPriorityQueue.poll()) != null) {
highPriorityEvents.add(event);
}
List<DownlinkMsg> downlinkMsgsPack = convertToDownlinkMsgsPack(highPriorityEvents);
sendDownlinkMsgsPack(downlinkMsgsPack).get();
} }
List<DownlinkMsg> downlinkMsgsPack = convertToDownlinkMsgsPack(highPriorityEvents);
sendDownlinkMsgsPack(downlinkMsgsPack).get();
} catch (Exception e) { } catch (Exception e) {
log.error("[{}] Failed to process high priority events", sessionId, e); log.error("[{}] Failed to process high priority events", sessionId, e);
} }

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

@ -343,7 +343,14 @@ public abstract class BaseEdgeProcessor {
protected void pushEntityEventToRuleEngine(TenantId tenantId, EntityId entityId, CustomerId customerId, protected void pushEntityEventToRuleEngine(TenantId tenantId, EntityId entityId, CustomerId customerId,
TbMsgType msgType, String msgData, TbMsgMetaData metaData) { TbMsgType msgType, String msgData, TbMsgMetaData metaData) {
TbMsg tbMsg = TbMsg.newMsg(msgType, entityId, customerId, metaData, TbMsgDataType.JSON, msgData); TbMsg tbMsg = TbMsg.newMsg()
.type(msgType)
.originator(entityId)
.customerId(customerId)
.copyMetaData(metaData)
.dataType(TbMsgDataType.JSON)
.data(msgData)
.build();
edgeCtx.getClusterService().pushMsgToRuleEngine(tenantId, entityId, tbMsg, new TbQueueCallback() { edgeCtx.getClusterService().pushMsgToRuleEngine(tenantId, entityId, tbMsg, new TbQueueCallback() {
@Override @Override
public void onSuccess(TbQueueMsgMetadata metadata) { public void onSuccess(TbQueueMsgMetadata metadata) {

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

@ -190,8 +190,13 @@ public abstract class DeviceEdgeProcessor extends BaseDeviceProcessor implements
ObjectNode data = JacksonUtil.newObjectNode(); ObjectNode data = JacksonUtil.newObjectNode();
data.put("method", deviceRpcCallMsg.getRequestMsg().getMethod()); data.put("method", deviceRpcCallMsg.getRequestMsg().getMethod());
data.put("params", deviceRpcCallMsg.getRequestMsg().getParams()); data.put("params", deviceRpcCallMsg.getRequestMsg().getParams());
TbMsg tbMsg = TbMsg.newMsg(TbMsgType.TO_SERVER_RPC_REQUEST, deviceId, null, metaData, TbMsg tbMsg = TbMsg.newMsg()
TbMsgDataType.JSON, JacksonUtil.toString(data)); .type(TbMsgType.TO_SERVER_RPC_REQUEST)
.originator(deviceId)
.copyMetaData(metaData)
.dataType(TbMsgDataType.JSON)
.data(JacksonUtil.toString(data))
.build();
edgeCtx.getClusterService().pushMsgToRuleEngine(tenantId, deviceId, tbMsg, new TbQueueCallback() { edgeCtx.getClusterService().pushMsgToRuleEngine(tenantId, deviceId, tbMsg, new TbQueueCallback() {
@Override @Override
public void onSuccess(TbQueueMsgMetadata metadata) { public void onSuccess(TbQueueMsgMetadata metadata) {

70
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java

@ -31,6 +31,7 @@ import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.AttributesSaveRequest;
import org.thingsboard.server.common.adaptor.JsonConverter; import org.thingsboard.server.common.adaptor.JsonConverter;
import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
@ -208,7 +209,15 @@ public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor {
JsonObject json = JsonUtils.getJsonObject(tsKv.getKvList()); JsonObject json = JsonUtils.getJsonObject(tsKv.getKvList());
metaData.putValue("ts", tsKv.getTs() + ""); metaData.putValue("ts", tsKv.getTs() + "");
var defaultQueueAndRuleChain = getDefaultQueueNameAndRuleChainId(tenantId, entityId); var defaultQueueAndRuleChain = getDefaultQueueNameAndRuleChainId(tenantId, entityId);
TbMsg tbMsg = TbMsg.newMsg(defaultQueueAndRuleChain.getKey(), TbMsgType.POST_TELEMETRY_REQUEST, entityId, customerId, metaData, gson.toJson(json), defaultQueueAndRuleChain.getValue(), null); TbMsg tbMsg = TbMsg.newMsg()
.queueName(defaultQueueAndRuleChain.getKey())
.type(TbMsgType.POST_TELEMETRY_REQUEST)
.originator(entityId)
.customerId(customerId)
.copyMetaData(metaData)
.data(gson.toJson(json))
.ruleChainId(defaultQueueAndRuleChain.getValue())
.build();
edgeCtx.getClusterService().pushMsgToRuleEngine(tenantId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() { edgeCtx.getClusterService().pushMsgToRuleEngine(tenantId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() {
@Override @Override
public void onSuccess(TbQueueMsgMetadata metadata) { public void onSuccess(TbQueueMsgMetadata metadata) {
@ -252,7 +261,15 @@ public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor {
SettableFuture<Void> futureToSet = SettableFuture.create(); SettableFuture<Void> futureToSet = SettableFuture.create();
JsonObject json = JsonUtils.getJsonObject(msg.getKvList()); JsonObject json = JsonUtils.getJsonObject(msg.getKvList());
var defaultQueueAndRuleChain = getDefaultQueueNameAndRuleChainId(tenantId, entityId); var defaultQueueAndRuleChain = getDefaultQueueNameAndRuleChainId(tenantId, entityId);
TbMsg tbMsg = TbMsg.newMsg(defaultQueueAndRuleChain.getKey(), TbMsgType.POST_ATTRIBUTES_REQUEST, entityId, customerId, metaData, gson.toJson(json), defaultQueueAndRuleChain.getValue(), null); TbMsg tbMsg = TbMsg.newMsg()
.queueName(defaultQueueAndRuleChain.getKey())
.type(TbMsgType.POST_ATTRIBUTES_REQUEST)
.originator(entityId)
.customerId(customerId)
.copyMetaData(metaData)
.data(gson.toJson(json))
.ruleChainId(defaultQueueAndRuleChain.getValue())
.build();
edgeCtx.getClusterService().pushMsgToRuleEngine(tenantId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() { edgeCtx.getClusterService().pushMsgToRuleEngine(tenantId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() {
@Override @Override
public void onSuccess(TbQueueMsgMetadata metadata) { public void onSuccess(TbQueueMsgMetadata metadata) {
@ -277,16 +294,36 @@ public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor {
JsonObject json = JsonUtils.getJsonObject(msg.getKvList()); JsonObject json = JsonUtils.getJsonObject(msg.getKvList());
List<AttributeKvEntry> attributes = new ArrayList<>(JsonConverter.convertToAttributes(json)); List<AttributeKvEntry> attributes = new ArrayList<>(JsonConverter.convertToAttributes(json));
String scope = metaData.getValue("scope"); String scope = metaData.getValue("scope");
tsSubService.saveAndNotify(tenantId, entityId, AttributeScope.valueOf(scope), attributes, new FutureCallback<Void>() { tsSubService.saveAttributes(AttributesSaveRequest.builder()
@Override .tenantId(tenantId)
public void onSuccess(@Nullable Void tmp) { .entityId(entityId)
var defaultQueueAndRuleChain = getDefaultQueueNameAndRuleChainId(tenantId, entityId); .scope(AttributeScope.valueOf(scope))
TbMsg tbMsg = TbMsg.newMsg(defaultQueueAndRuleChain.getKey(), TbMsgType.ATTRIBUTES_UPDATED, entityId, .entries(attributes)
customerId, metaData, gson.toJson(json), defaultQueueAndRuleChain.getValue(), null); .callback(new FutureCallback<>() {
edgeCtx.getClusterService().pushMsgToRuleEngine(tenantId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() {
@Override @Override
public void onSuccess(TbQueueMsgMetadata metadata) { public void onSuccess(@Nullable Void tmp) {
futureToSet.set(null); var defaultQueueAndRuleChain = getDefaultQueueNameAndRuleChainId(tenantId, entityId);
TbMsg tbMsg = TbMsg.newMsg()
.queueName(defaultQueueAndRuleChain.getKey())
.type(TbMsgType.ATTRIBUTES_UPDATED)
.originator(entityId)
.customerId(customerId)
.copyMetaData(metaData)
.data(gson.toJson(json))
.ruleChainId(defaultQueueAndRuleChain.getValue())
.build();
edgeCtx.getClusterService().pushMsgToRuleEngine(tenantId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() {
@Override
public void onSuccess(TbQueueMsgMetadata metadata) {
futureToSet.set(null);
}
@Override
public void onFailure(Throwable t) {
log.error("[{}] Can't process attributes update [{}]", tenantId, msg, t);
futureToSet.setException(t);
}
});
} }
@Override @Override
@ -294,15 +331,8 @@ public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor {
log.error("[{}] Can't process attributes update [{}]", tenantId, msg, t); log.error("[{}] Can't process attributes update [{}]", tenantId, msg, t);
futureToSet.setException(t); futureToSet.setException(t);
} }
}); })
} .build());
@Override
public void onFailure(Throwable t) {
log.error("[{}] Can't process attributes update [{}]", tenantId, msg, t);
futureToSet.setException(t);
}
});
return futureToSet; return futureToSet;
} }

10
application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java

@ -250,8 +250,14 @@ public class EntityStateSourcingListener {
private void pushAssignedFromNotification(Tenant currentTenant, TenantId newTenantId, Device assignedDevice) { private void pushAssignedFromNotification(Tenant currentTenant, TenantId newTenantId, Device assignedDevice) {
String data = JacksonUtil.toString(JacksonUtil.valueToTree(assignedDevice)); String data = JacksonUtil.toString(JacksonUtil.valueToTree(assignedDevice));
if (data != null) { if (data != null) {
TbMsg tbMsg = TbMsg.newMsg(TbMsgType.ENTITY_ASSIGNED_FROM_TENANT, assignedDevice.getId(), TbMsg tbMsg = TbMsg.newMsg()
assignedDevice.getCustomerId(), getMetaDataForAssignedFrom(currentTenant), TbMsgDataType.JSON, data); .type(TbMsgType.ENTITY_ASSIGNED_FROM_TENANT)
.originator(assignedDevice.getId())
.customerId(assignedDevice.getCustomerId())
.copyMetaData(getMetaDataForAssignedFrom(currentTenant))
.dataType(TbMsgDataType.JSON)
.data(data)
.build();
tbClusterService.pushMsgToRuleEngine(newTenantId, assignedDevice.getId(), tbMsg, null); tbClusterService.pushMsgToRuleEngine(newTenantId, assignedDevice.getId(), tbMsg, null);
} }
} }

204
application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java

@ -25,6 +25,10 @@ import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.ConcurrentReferenceHashMap; import org.springframework.util.ConcurrentReferenceHashMap;
import org.thingsboard.rule.engine.api.AttributesDeleteRequest;
import org.thingsboard.rule.engine.api.AttributesSaveRequest;
import org.thingsboard.rule.engine.api.TimeseriesDeleteRequest;
import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
@ -55,6 +59,7 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -273,36 +278,41 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen
return Futures.transform(getAttrFuture, attributeKvEntries -> { return Futures.transform(getAttrFuture, attributeKvEntries -> {
List<AttributeKvEntry> attributes; List<AttributeKvEntry> attributes;
if (attributeKvEntries != null && !attributeKvEntries.isEmpty()) { if (attributeKvEntries != null && !attributeKvEntries.isEmpty()) {
attributes = attributes = attributeKvEntries.stream()
attributeKvEntries.stream() .filter(attributeKvEntry -> {
.filter(attributeKvEntry -> { long startTime = entityView.getStartTimeMs();
long startTime = entityView.getStartTimeMs(); long endTime = entityView.getEndTimeMs();
long endTime = entityView.getEndTimeMs(); long lastUpdateTs = attributeKvEntry.getLastUpdateTs();
long lastUpdateTs = attributeKvEntry.getLastUpdateTs(); return startTime == 0 && endTime == 0 ||
return startTime == 0 && endTime == 0 || (endTime == 0 && startTime < lastUpdateTs) ||
(endTime == 0 && startTime < lastUpdateTs) || (startTime == 0 && endTime > lastUpdateTs) ||
(startTime == 0 && endTime > lastUpdateTs) || (startTime < lastUpdateTs && endTime > lastUpdateTs);
(startTime < lastUpdateTs && endTime > lastUpdateTs); }).collect(Collectors.toList());
}).collect(Collectors.toList()); tsSubService.saveAttributes(AttributesSaveRequest.builder()
tsSubService.saveAndNotify(entityView.getTenantId(), entityId, scope, attributes, new FutureCallback<Void>() { .tenantId(entityView.getTenantId())
@Override .entityId(entityId)
public void onSuccess(@Nullable Void tmp) { .scope(scope)
try { .entries(attributes)
logAttributesUpdated(entityView.getTenantId(), user, entityId, scope, attributes, null); .callback(new FutureCallback<>() {
} catch (ThingsboardException e) { @Override
log.error("Failed to log attribute updates", e); public void onSuccess(@Nullable Void tmp) {
} try {
} logAttributesUpdated(entityView.getTenantId(), user, entityId, scope, attributes, null);
} catch (ThingsboardException e) {
@Override log.error("Failed to log attribute updates", e);
public void onFailure(Throwable t) { }
try { }
logAttributesUpdated(entityView.getTenantId(), user, entityId, scope, attributes, t);
} catch (ThingsboardException e) { @Override
log.error("Failed to log attribute updates", e); public void onFailure(Throwable t) {
} try {
} logAttributesUpdated(entityView.getTenantId(), user, entityId, scope, attributes, t);
}); } catch (ThingsboardException e) {
log.error("Failed to log attribute updates", e);
}
}
})
.build());
} }
return null; return null;
}, MoreExecutors.directExecutor()); }, MoreExecutors.directExecutor());
@ -334,15 +344,22 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen
}, MoreExecutors.directExecutor()); }, MoreExecutors.directExecutor());
return Futures.transform(latestFuture, latestValues -> { return Futures.transform(latestFuture, latestValues -> {
if (latestValues != null && !latestValues.isEmpty()) { if (latestValues != null && !latestValues.isEmpty()) {
tsSubService.saveLatestAndNotify(entityView.getTenantId(), entityId, latestValues, new FutureCallback<Void>() { tsSubService.saveTimeseries(TimeseriesSaveRequest.builder()
@Override .tenantId(entityView.getTenantId())
public void onSuccess(@Nullable Void tmp) { .entityId(entityId)
} .entries(latestValues)
.onlyLatest(true)
.callback(new FutureCallback<Void>() {
@Override
public void onSuccess(@Nullable Void tmp) {
}
@Override @Override
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
} log.error("[{}][{}] Failed to save entity view latest timeseries: {}", tenantId, entityView.getId(), latestValues, t);
}); }
})
.build());
} }
return null; return null;
}, MoreExecutors.directExecutor()); }, MoreExecutors.directExecutor());
@ -352,27 +369,33 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen
EntityViewId entityId = entityView.getId(); EntityViewId entityId = entityView.getId();
SettableFuture<Void> resultFuture = SettableFuture.create(); SettableFuture<Void> resultFuture = SettableFuture.create();
if (keys != null && !keys.isEmpty()) { if (keys != null && !keys.isEmpty()) {
tsSubService.deleteAndNotify(entityView.getTenantId(), entityId, scope, keys, new FutureCallback<Void>() { tsSubService.deleteAttributes(AttributesDeleteRequest.builder()
@Override .tenantId(entityView.getTenantId())
public void onSuccess(@Nullable Void tmp) { .entityId(entityId)
try { .scope(scope)
logAttributesDeleted(entityView.getTenantId(), user, entityId, scope, keys, null); .keys(keys)
} catch (ThingsboardException e) { .callback(new FutureCallback<>() {
log.error("Failed to log attribute delete", e); @Override
} public void onSuccess(@Nullable Void tmp) {
resultFuture.set(tmp); try {
} logAttributesDeleted(entityView.getTenantId(), user, entityId, scope, keys, null);
} catch (ThingsboardException e) {
log.error("Failed to log attribute delete", e);
}
resultFuture.set(tmp);
}
@Override @Override
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
try { try {
logAttributesDeleted(entityView.getTenantId(), user, entityId, scope, keys, t); logAttributesDeleted(entityView.getTenantId(), user, entityId, scope, keys, t);
} catch (ThingsboardException e) { } catch (ThingsboardException e) {
log.error("Failed to log attribute delete", e); log.error("Failed to log attribute delete", e);
} }
resultFuture.setException(t); resultFuture.setException(t);
} }
}); })
.build());
} else { } else {
resultFuture.set(null); resultFuture.set(null);
} }
@ -382,51 +405,32 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen
private ListenableFuture<Void> deleteLatestFromEntityView(EntityView entityView, List<String> keys, User user) { private ListenableFuture<Void> deleteLatestFromEntityView(EntityView entityView, List<String> keys, User user) {
EntityViewId entityId = entityView.getId(); EntityViewId entityId = entityView.getId();
SettableFuture<Void> resultFuture = SettableFuture.create(); SettableFuture<Void> resultFuture = SettableFuture.create();
if (keys != null && !keys.isEmpty()) { tsSubService.deleteTimeseries(TimeseriesDeleteRequest.builder()
tsSubService.deleteLatest(entityView.getTenantId(), entityId, keys, new FutureCallback<Void>() { .tenantId(entityView.getTenantId())
@Override .entityId(entityId)
public void onSuccess(@Nullable Void tmp) { .keys(keys)
try { .callback(new FutureCallback<>() {
logTimeseriesDeleted(entityView.getTenantId(), user, entityId, keys, null); @Override
} catch (ThingsboardException e) { public void onSuccess(@Nullable List<String> result) {
log.error("Failed to log timeseries delete", e); try {
} logTimeseriesDeleted(entityView.getTenantId(), user, entityId, result, null);
resultFuture.set(tmp); } catch (ThingsboardException e) {
} log.error("Failed to log timeseries delete", e);
}
@Override resultFuture.set(null);
public void onFailure(Throwable t) {
try {
logTimeseriesDeleted(entityView.getTenantId(), user, entityId, keys, t);
} catch (ThingsboardException e) {
log.error("Failed to log timeseries delete", e);
}
resultFuture.setException(t);
}
});
} else {
tsSubService.deleteAllLatest(entityView.getTenantId(), entityId, new FutureCallback<Collection<String>>() {
@Override
public void onSuccess(@Nullable Collection<String> keys) {
try {
logTimeseriesDeleted(entityView.getTenantId(), user, entityId, new ArrayList<>(keys), null);
} catch (ThingsboardException e) {
log.error("Failed to log timeseries delete", e);
} }
resultFuture.set(null);
}
@Override @Override
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
try { try {
logTimeseriesDeleted(entityView.getTenantId(), user, entityId, Collections.emptyList(), t); logTimeseriesDeleted(entityView.getTenantId(), user, entityId, Optional.ofNullable(keys).orElse(Collections.emptyList()), t);
} catch (ThingsboardException e) { } catch (ThingsboardException e) {
log.error("Failed to log timeseries delete", e); log.error("Failed to log timeseries delete", e);
}
resultFuture.setException(t);
} }
resultFuture.setException(t); })
} .build());
});
}
return resultFuture; return resultFuture;
} }

6
application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java

@ -149,7 +149,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
return; // if originated by rule - just ignore delivery method return; // if originated by rule - just ignore delivery method
} }
} }
if (ruleId == null) { if (ruleId == null && !notificationTemplate.getNotificationType().isSystem()) {
if (targets.stream().noneMatch(target -> target.getConfiguration().getType().getSupportedDeliveryMethods().contains(deliveryMethod))) { if (targets.stream().noneMatch(target -> target.getConfiguration().getType().getSupportedDeliveryMethods().contains(deliveryMethod))) {
throw new IllegalArgumentException("Recipients for " + deliveryMethod.getName() + " delivery method not chosen"); throw new IllegalArgumentException("Recipients for " + deliveryMethod.getName() + " delivery method not chosen");
} }
@ -225,13 +225,13 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
NotificationTemplate notificationTemplate = notificationTemplateService.findTenantOrSystemNotificationTemplate(tenantId, type) NotificationTemplate notificationTemplate = notificationTemplateService.findTenantOrSystemNotificationTemplate(tenantId, type)
.orElseThrow(() -> new IllegalArgumentException("No notification template found for type " + type)); .orElseThrow(() -> new IllegalArgumentException("No notification template found for type " + type));
NotificationRequest notificationRequest = NotificationRequest.builder() NotificationRequest notificationRequest = NotificationRequest.builder()
.tenantId(TenantId.SYS_TENANT_ID) .tenantId(tenantId)
.targets(List.of(targetId.getId())) .targets(List.of(targetId.getId()))
.templateId(notificationTemplate.getId()) .templateId(notificationTemplate.getId())
.info(info) .info(info)
.originatorEntityId(TenantId.SYS_TENANT_ID) .originatorEntityId(TenantId.SYS_TENANT_ID)
.build(); .build();
processNotificationRequest(TenantId.SYS_TENANT_ID, notificationRequest, null); processNotificationRequest(tenantId, notificationRequest, null);
} }
private void processNotificationRequestAsync(NotificationProcessingContext ctx, List<NotificationTarget> targets, FutureCallback<NotificationRequestStats> callback) { private void processNotificationRequestAsync(NotificationProcessingContext ctx, List<NotificationTarget> targets, FutureCallback<NotificationRequestStats> callback) {

26
application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessor.java

@ -54,7 +54,6 @@ import java.util.EnumMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@ -106,22 +105,11 @@ public class DefaultNotificationRuleProcessor implements NotificationRuleProcess
log.debug("Processing notification rule '{}' for trigger type {}", rule.getName(), rule.getTriggerType()); log.debug("Processing notification rule '{}' for trigger type {}", rule.getName(), rule.getTriggerType());
if (matchesClearRule(trigger, triggerConfig)) { if (matchesClearRule(trigger, triggerConfig)) {
List<NotificationRequest> notificationRequests = findAlreadySentNotificationRequests(rule, trigger); List<NotificationRequest> scheduledRequests = notificationRequestService.findNotificationRequestsByRuleIdAndOriginatorEntityIdAndStatus(
if (notificationRequests.isEmpty()) { rule.getTenantId(), rule.getId(), trigger.getOriginatorEntityId(), NotificationRequestStatus.SCHEDULED
return; );
} scheduledRequests.forEach(notificationRequest -> {
notificationCenter.deleteNotificationRequest(rule.getTenantId(), notificationRequest.getId());
List<UUID> targets = notificationRequests.stream()
.filter(NotificationRequest::isSent)
.flatMap(notificationRequest -> notificationRequest.getTargets().stream())
.distinct().collect(Collectors.toList());
NotificationInfo notificationInfo = constructNotificationInfo(trigger, triggerConfig);
submitNotificationRequest(targets, rule, trigger.getOriginatorEntityId(), notificationInfo, 0);
notificationRequests.forEach(notificationRequest -> {
if (notificationRequest.isScheduled()) {
notificationCenter.deleteNotificationRequest(rule.getTenantId(), notificationRequest.getId());
}
}); });
return; return;
} }
@ -139,10 +127,6 @@ public class DefaultNotificationRuleProcessor implements NotificationRuleProcess
} }
} }
private List<NotificationRequest> findAlreadySentNotificationRequests(NotificationRule rule, NotificationRuleTrigger trigger) {
return notificationRequestService.findNotificationRequestsByRuleIdAndOriginatorEntityId(rule.getTenantId(), rule.getId(), trigger.getOriginatorEntityId());
}
private void submitNotificationRequest(List<UUID> targets, NotificationRule rule, private void submitNotificationRequest(List<UUID> targets, NotificationRule rule,
EntityId originatorEntityId, NotificationInfo notificationInfo, int delayInSec) { EntityId originatorEntityId, NotificationInfo notificationInfo, int delayInSec) {
NotificationRequestConfig config = new NotificationRequestConfig(); NotificationRequestConfig config = new NotificationRequestConfig();

4
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmTriggerProcessor.java

@ -46,10 +46,10 @@ public class AlarmTriggerProcessor implements NotificationRuleTriggerProcessor<A
if (triggerConfig.getNotifyOn().contains(AlarmAction.CREATED)) { if (triggerConfig.getNotifyOn().contains(AlarmAction.CREATED)) {
return severityMatches(alarm, triggerConfig); return severityMatches(alarm, triggerConfig);
} }
} else if (alarmUpdate.isSeverityChanged()) { } else if (alarmUpdate.isSeverityChanged()) {
if (triggerConfig.getNotifyOn().contains(AlarmAction.SEVERITY_CHANGED)) { if (triggerConfig.getNotifyOn().contains(AlarmAction.SEVERITY_CHANGED)) {
return severityMatches(alarmUpdate.getOld(), triggerConfig) || severityMatches(alarm, triggerConfig); return severityMatches(alarmUpdate.getOld(), triggerConfig) || severityMatches(alarm, triggerConfig);
} else { } else {
// if we haven't yet sent notification about the alarm // if we haven't yet sent notification about the alarm
return !severityMatches(alarmUpdate.getOld(), triggerConfig) && severityMatches(alarm, triggerConfig); return !severityMatches(alarmUpdate.getOld(), triggerConfig) && severityMatches(alarm, triggerConfig);
} }

100
application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java

@ -20,7 +20,10 @@ import jakarta.annotation.Nullable;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.AttributesDeleteRequest;
import org.thingsboard.rule.engine.api.AttributesSaveRequest;
import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
@ -54,7 +57,6 @@ import org.thingsboard.server.queue.provider.TbCoreQueueFactory;
import org.thingsboard.server.queue.provider.TbRuleEngineQueueFactory; import org.thingsboard.server.queue.provider.TbRuleEngineQueueFactory;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
@ -128,7 +130,7 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
// Device was updated and new firmware is different from previous firmware. // Device was updated and new firmware is different from previous firmware.
send(device.getTenantId(), device.getId(), newFirmwareId, System.currentTimeMillis(), FIRMWARE); send(device.getTenantId(), device.getId(), newFirmwareId, System.currentTimeMillis(), FIRMWARE);
} }
} else if (oldFirmwareId != null){ } else if (oldFirmwareId != null) {
// Device was updated and new firmware is not set. // Device was updated and new firmware is not set.
remove(device, FIRMWARE); remove(device, FIRMWARE);
} }
@ -155,7 +157,7 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
// Device was updated and new firmware is different from previous firmware. // Device was updated and new firmware is different from previous firmware.
send(device.getTenantId(), device.getId(), newSoftwareId, System.currentTimeMillis(), SOFTWARE); send(device.getTenantId(), device.getId(), newSoftwareId, System.currentTimeMillis(), SOFTWARE);
} }
} else if (oldSoftwareId != null){ } else if (oldSoftwareId != null) {
// Device was updated and new firmware is not set. // Device was updated and new firmware is not set.
remove(device, SOFTWARE); remove(device, SOFTWARE);
} }
@ -261,17 +263,22 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
telemetry.add(new BasicTsKvEntry(ts, new LongDataEntry(getTargetTelemetryKey(firmware.getType(), TS), ts))); telemetry.add(new BasicTsKvEntry(ts, new LongDataEntry(getTargetTelemetryKey(firmware.getType(), TS), ts)));
telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(getTelemetryKey(firmware.getType(), STATE), OtaPackageUpdateStatus.QUEUED.name()))); telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(getTelemetryKey(firmware.getType(), STATE), OtaPackageUpdateStatus.QUEUED.name())));
telemetryService.saveAndNotify(tenantId, deviceId, telemetry, new FutureCallback<>() { telemetryService.saveTimeseries(TimeseriesSaveRequest.builder()
@Override .tenantId(tenantId)
public void onSuccess(@Nullable Void tmp) { .entityId(deviceId)
log.trace("[{}] Success save firmware status!", deviceId); .entries(telemetry)
} .callback(new FutureCallback<Void>() {
@Override
public void onSuccess(@Nullable Void tmp) {
log.trace("[{}] Success save firmware status!", deviceId);
}
@Override @Override
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
log.error("[{}] Failed to save firmware status!", deviceId, t); log.error("[{}] Failed to save firmware status!", deviceId, t);
} }
}); })
.build());
} }
@ -282,19 +289,24 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
BasicTsKvEntry status = new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry(getTelemetryKey(otaPackageType, STATE), OtaPackageUpdateStatus.INITIATED.name())); BasicTsKvEntry status = new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry(getTelemetryKey(otaPackageType, STATE), OtaPackageUpdateStatus.INITIATED.name()));
telemetryService.saveAndNotify(tenantId, deviceId, Collections.singletonList(status), new FutureCallback<>() { telemetryService.saveTimeseries(TimeseriesSaveRequest.builder()
@Override .tenantId(tenantId)
public void onSuccess(@Nullable Void tmp) { .entityId(deviceId)
log.trace("[{}] Success save telemetry with target {} for device!", deviceId, otaPackage); .entry(status)
updateAttributes(device, otaPackage, ts, tenantId, deviceId, otaPackageType); .callback(new FutureCallback<>() {
} @Override
public void onSuccess(@Nullable Void tmp) {
log.trace("[{}] Success save telemetry with target {} for device!", deviceId, otaPackage);
updateAttributes(device, otaPackage, ts, tenantId, deviceId, otaPackageType);
}
@Override @Override
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
log.error("[{}] Failed to save telemetry with target {} for device!", deviceId, otaPackage, t); log.error("[{}] Failed to save telemetry with target {} for device!", deviceId, otaPackage, t);
updateAttributes(device, otaPackage, ts, tenantId, deviceId, otaPackageType); updateAttributes(device, otaPackage, ts, tenantId, deviceId, otaPackageType);
} }
}); })
.build());
} }
private void updateAttributes(Device device, OtaPackageInfo otaPackage, long ts, TenantId tenantId, DeviceId deviceId, OtaPackageType otaPackageType) { private void updateAttributes(Device device, OtaPackageInfo otaPackage, long ts, TenantId tenantId, DeviceId deviceId, OtaPackageType otaPackageType) {
@ -336,17 +348,23 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
remove(device, otaPackageType, attrToRemove); remove(device, otaPackageType, attrToRemove);
telemetryService.saveAndNotify(tenantId, deviceId, AttributeScope.SHARED_SCOPE, attributes, new FutureCallback<>() { telemetryService.saveAttributes(AttributesSaveRequest.builder()
@Override .tenantId(tenantId)
public void onSuccess(@Nullable Void tmp) { .entityId(deviceId)
log.trace("[{}] Success save attributes with target firmware!", deviceId); .scope(AttributeScope.SHARED_SCOPE)
} .entries(attributes)
.callback(new FutureCallback<>() {
@Override
public void onSuccess(@Nullable Void tmp) {
log.trace("[{}] Success save attributes with target firmware!", deviceId);
}
@Override @Override
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
log.error("[{}] Failed to save attributes with target firmware!", deviceId, t); log.error("[{}] Failed to save attributes with target firmware!", deviceId, t);
} }
}); })
.build());
} }
private void remove(Device device, OtaPackageType otaPackageType) { private void remove(Device device, OtaPackageType otaPackageType) {
@ -354,8 +372,12 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
} }
private void remove(Device device, OtaPackageType otaPackageType, List<String> attributesKeys) { private void remove(Device device, OtaPackageType otaPackageType, List<String> attributesKeys) {
telemetryService.deleteAndNotify(device.getTenantId(), device.getId(), AttributeScope.SHARED_SCOPE, attributesKeys, telemetryService.deleteAttributes(AttributesDeleteRequest.builder()
new FutureCallback<>() { .tenantId(device.getTenantId())
.entityId(device.getId())
.scope(AttributeScope.SHARED_SCOPE)
.keys(attributesKeys)
.callback(new FutureCallback<>() {
@Override @Override
public void onSuccess(@Nullable Void tmp) { public void onSuccess(@Nullable Void tmp) {
log.trace("[{}] Success remove target {} attributes!", device.getId(), otaPackageType); log.trace("[{}] Success remove target {} attributes!", device.getId(), otaPackageType);
@ -366,6 +388,8 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
log.error("[{}] Failed to remove target {} attributes!", device.getId(), otaPackageType, t); log.error("[{}] Failed to remove target {} attributes!", device.getId(), otaPackageType, t);
} }
}); })
.build());
} }
} }

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

@ -290,11 +290,16 @@ public class DefaultTbClusterService implements TbClusterService {
boolean isQueueTransform = targetQueueName != null && !targetQueueName.equals(tbMsg.getQueueName()); boolean isQueueTransform = targetQueueName != null && !targetQueueName.equals(tbMsg.getQueueName());
if (isRuleChainTransform && isQueueTransform) { if (isRuleChainTransform && isQueueTransform) {
tbMsg = TbMsg.transformMsg(tbMsg, targetRuleChainId, targetQueueName); tbMsg = tbMsg.transform()
.queueName(targetQueueName)
.ruleChainId(targetRuleChainId)
.build();
} else if (isRuleChainTransform) { } else if (isRuleChainTransform) {
tbMsg = TbMsg.transformMsgRuleChainId(tbMsg, targetRuleChainId); tbMsg = tbMsg.transform()
.ruleChainId(targetRuleChainId)
.build();
} else if (isQueueTransform) { } else if (isQueueTransform) {
tbMsg = TbMsg.transformMsgQueueName(tbMsg, targetQueueName); tbMsg = tbMsg.transform(targetQueueName);
} }
} }
return tbMsg; return tbMsg;

9
application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbCoreDeviceRpcService.java

@ -183,7 +183,14 @@ public class DefaultTbCoreDeviceRpcService implements TbCoreDeviceRpcService {
entityNode.put(DataConstants.ADDITIONAL_INFO, msg.getAdditionalInfo()); entityNode.put(DataConstants.ADDITIONAL_INFO, msg.getAdditionalInfo());
try { try {
TbMsg tbMsg = TbMsg.newMsg(TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE, msg.getDeviceId(), Optional.ofNullable(currentUser).map(User::getCustomerId).orElse(null), metaData, TbMsgDataType.JSON, JacksonUtil.toString(entityNode)); TbMsg tbMsg = TbMsg.newMsg()
.type(TbMsgType.RPC_CALL_FROM_SERVER_TO_DEVICE)
.originator(msg.getDeviceId())
.customerId(Optional.ofNullable(currentUser).map(User::getCustomerId).orElse(null))
.copyMetaData(metaData)
.dataType(TbMsgDataType.JSON)
.data(JacksonUtil.toString(entityNode))
.build();
clusterService.pushMsgToRuleEngine(msg.getTenantId(), msg.getDeviceId(), tbMsg, null); clusterService.pushMsgToRuleEngine(msg.getTenantId(), msg.getDeviceId(), tbMsg, null);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
throw new RuntimeException(e); throw new RuntimeException(e);

7
application/src/main/java/org/thingsboard/server/service/rpc/TbRpcService.java

@ -63,7 +63,12 @@ public class TbRpcService {
} }
private void pushRpcMsgToRuleEngine(TenantId tenantId, Rpc rpc) { private void pushRpcMsgToRuleEngine(TenantId tenantId, Rpc rpc) {
TbMsg msg = TbMsg.newMsg(TbMsgType.valueOf("RPC_" + rpc.getStatus().name()), rpc.getDeviceId(), TbMsgMetaData.EMPTY, JacksonUtil.toString(rpc)); TbMsg msg = TbMsg.newMsg()
.type(TbMsgType.valueOf("RPC_" + rpc.getStatus().name()))
.originator(rpc.getDeviceId())
.copyMetaData(TbMsgMetaData.EMPTY)
.data(JacksonUtil.toString(rpc))
.build();
tbClusterService.pushMsgToRuleEngine(tenantId, rpc.getDeviceId(), msg, null); tbClusterService.pushMsgToRuleEngine(tenantId, rpc.getDeviceId(), msg, null);
} }

6
application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java

@ -147,6 +147,10 @@ public class RuleNodeJsScriptEngine extends RuleNodeScriptEngine<JsInvokeService
String newData = data != null ? data : msg.getData(); String newData = data != null ? data : msg.getData();
TbMsgMetaData newMetadata = metadata != null ? new TbMsgMetaData(metadata) : msg.getMetaData().copy(); TbMsgMetaData newMetadata = metadata != null ? new TbMsgMetaData(metadata) : msg.getMetaData().copy();
String newMessageType = !StringUtils.isEmpty(messageType) ? messageType : msg.getType(); String newMessageType = !StringUtils.isEmpty(messageType) ? messageType : msg.getType();
return TbMsg.transformMsg(msg, newMessageType, msg.getOriginator(), newMetadata, newData); return msg.transform()
.type(newMessageType)
.metaData(newMetadata)
.data(newData)
.build();
} }
} }

6
application/src/main/java/org/thingsboard/server/service/script/RuleNodeTbelScriptEngine.java

@ -156,7 +156,11 @@ public class RuleNodeTbelScriptEngine extends RuleNodeScriptEngine<TbelInvokeSer
String newData = data != null ? data : msg.getData(); String newData = data != null ? data : msg.getData();
TbMsgMetaData newMetadata = metadata != null ? new TbMsgMetaData(metadata) : msg.getMetaData().copy(); TbMsgMetaData newMetadata = metadata != null ? new TbMsgMetaData(metadata) : msg.getMetaData().copy();
String newMessageType = !StringUtils.isEmpty(messageType) ? messageType : msg.getType(); String newMessageType = !StringUtils.isEmpty(messageType) ? messageType : msg.getType();
return TbMsg.transformMsg(msg, newMessageType, msg.getOriginator(), newMetadata, newData); return msg.transform()
.type(newMessageType)
.metaData(newMetadata)
.data(newData)
.build();
} }
private static <T> ListenableFuture<T> wrongResultType(Object result) { private static <T> ListenableFuture<T> wrongResultType(Object result) {

56
application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java

@ -38,6 +38,8 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.rule.engine.api.AttributesSaveRequest;
import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.AttributeScope;
@ -51,6 +53,7 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UUIDBased; import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.KvEntry;
@ -857,7 +860,14 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev
if (!persistToTelemetry) { if (!persistToTelemetry) {
md.putValue(SCOPE, SERVER_SCOPE); md.putValue(SCOPE, SERVER_SCOPE);
} }
TbMsg tbMsg = TbMsg.newMsg(msgType, stateData.getDeviceId(), stateData.getCustomerId(), md, TbMsgDataType.JSON, data); TbMsg tbMsg = TbMsg.newMsg()
.type(msgType)
.originator(stateData.getDeviceId())
.customerId(stateData.getCustomerId())
.copyMetaData(md)
.dataType(TbMsgDataType.JSON)
.data(data)
.build();
clusterService.pushMsgToRuleEngine(stateData.getTenantId(), stateData.getDeviceId(), tbMsg, null); clusterService.pushMsgToRuleEngine(stateData.getTenantId(), stateData.getDeviceId(), tbMsg, null);
} catch (Exception e) { } catch (Exception e) {
log.warn("[{}] Failed to push inactivity alarm: {}", stateData.getDeviceId(), state, e); log.warn("[{}] Failed to push inactivity alarm: {}", stateData.getDeviceId(), state, e);
@ -865,24 +875,30 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev
} }
private void save(DeviceId deviceId, String key, long value) { private void save(DeviceId deviceId, String key, long value) {
if (persistToTelemetry) { save(deviceId, new LongDataEntry(key, value), getCurrentTimeMillis());
tsSubService.saveAndNotifyInternal(
TenantId.SYS_TENANT_ID, deviceId,
Collections.singletonList(new BasicTsKvEntry(getCurrentTimeMillis(), new LongDataEntry(key, value))),
telemetryTtl, new TelemetrySaveCallback<>(deviceId, key, value));
} else {
tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, AttributeScope.SERVER_SCOPE, key, value, new TelemetrySaveCallback<>(deviceId, key, value));
}
} }
private void save(DeviceId deviceId, String key, boolean value) { private void save(DeviceId deviceId, String key, boolean value) {
save(deviceId, new BooleanDataEntry(key, value), getCurrentTimeMillis());
}
private void save(DeviceId deviceId, KvEntry kvEntry, long ts) {
if (persistToTelemetry) { if (persistToTelemetry) {
tsSubService.saveAndNotifyInternal( tsSubService.saveTimeseriesInternal(TimeseriesSaveRequest.builder()
TenantId.SYS_TENANT_ID, deviceId, .tenantId(TenantId.SYS_TENANT_ID)
Collections.singletonList(new BasicTsKvEntry(getCurrentTimeMillis(), new BooleanDataEntry(key, value))), .entityId(deviceId)
telemetryTtl, new TelemetrySaveCallback<>(deviceId, key, value)); .entry(new BasicTsKvEntry(ts, kvEntry))
.ttl(telemetryTtl)
.callback(new TelemetrySaveCallback<>(deviceId, kvEntry))
.build());
} else { } else {
tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, AttributeScope.SERVER_SCOPE, key, value, new TelemetrySaveCallback<>(deviceId, key, value)); tsSubService.saveAttributes(AttributesSaveRequest.builder()
.tenantId(TenantId.SYS_TENANT_ID)
.entityId(deviceId)
.scope(AttributeScope.SERVER_SCOPE)
.entry(new BaseAttributeKvEntry(ts, kvEntry))
.callback(new TelemetrySaveCallback<>(deviceId, kvEntry))
.build());
} }
} }
@ -892,23 +908,21 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev
private static class TelemetrySaveCallback<T> implements FutureCallback<T> { private static class TelemetrySaveCallback<T> implements FutureCallback<T> {
private final DeviceId deviceId; private final DeviceId deviceId;
private final String key; private final KvEntry kvEntry;
private final Object value;
TelemetrySaveCallback(DeviceId deviceId, String key, Object value) { TelemetrySaveCallback(DeviceId deviceId, KvEntry kvEntry) {
this.deviceId = deviceId; this.deviceId = deviceId;
this.key = key; this.kvEntry = kvEntry;
this.value = value;
} }
@Override @Override
public void onSuccess(@Nullable T result) { public void onSuccess(@Nullable T result) {
log.trace("[{}] Successfully updated attribute [{}] with value [{}]", deviceId, key, value); log.trace("[{}] Successfully updated entry {}", deviceId, kvEntry);
} }
@Override @Override
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
log.warn("[{}] Failed to update attribute [{}] with value [{}]", deviceId, key, value, t); log.warn("[{}] Failed to update entry {}", deviceId, kvEntry, t);
} }
} }
} }

22
application/src/main/java/org/thingsboard/server/service/stats/DefaultRuleEngineStatisticsService.java

@ -22,6 +22,7 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
import org.thingsboard.server.common.data.id.QueueStatsId; import org.thingsboard.server.common.data.id.QueueStatsId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
@ -37,7 +38,6 @@ import org.thingsboard.server.queue.util.TbRuleEngineComponent;
import org.thingsboard.server.service.queue.TbRuleEngineConsumerStats; import org.thingsboard.server.service.queue.TbRuleEngineConsumerStats;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
@ -53,9 +53,9 @@ import java.util.stream.Collectors;
public class DefaultRuleEngineStatisticsService implements RuleEngineStatisticsService { public class DefaultRuleEngineStatisticsService implements RuleEngineStatisticsService {
public static final String RULE_ENGINE_EXCEPTION = "ruleEngineException"; public static final String RULE_ENGINE_EXCEPTION = "ruleEngineException";
public static final FutureCallback<Integer> CALLBACK = new FutureCallback<Integer>() { public static final FutureCallback<Void> CALLBACK = new FutureCallback<Void>() {
@Override @Override
public void onSuccess(@Nullable Integer result) { public void onSuccess(@Nullable Void result) {
} }
@ -89,7 +89,13 @@ public class DefaultRuleEngineStatisticsService implements RuleEngineStatisticsS
if (!tsList.isEmpty()) { if (!tsList.isEmpty()) {
long ttl = apiLimitService.getLimit(tenantId, DefaultTenantProfileConfiguration::getQueueStatsTtlDays); long ttl = apiLimitService.getLimit(tenantId, DefaultTenantProfileConfiguration::getQueueStatsTtlDays);
ttl = TimeUnit.DAYS.toSeconds(ttl); ttl = TimeUnit.DAYS.toSeconds(ttl);
tsService.saveAndNotifyInternal(tenantId, queueStatsId, tsList, ttl, CALLBACK); tsService.saveTimeseriesInternal(TimeseriesSaveRequest.builder()
.tenantId(tenantId)
.entityId(queueStatsId)
.entries(tsList)
.ttl(ttl)
.callback(CALLBACK)
.build());
} }
} }
} catch (Exception e) { } catch (Exception e) {
@ -103,7 +109,13 @@ public class DefaultRuleEngineStatisticsService implements RuleEngineStatisticsS
TsKvEntry tsKv = new BasicTsKvEntry(e.getTs(), new JsonDataEntry(RULE_ENGINE_EXCEPTION, e.toJsonString(maxErrorMessageLength))); TsKvEntry tsKv = new BasicTsKvEntry(e.getTs(), new JsonDataEntry(RULE_ENGINE_EXCEPTION, e.toJsonString(maxErrorMessageLength)));
long ttl = apiLimitService.getLimit(tenantId, DefaultTenantProfileConfiguration::getRuleEngineExceptionsTtlDays); long ttl = apiLimitService.getLimit(tenantId, DefaultTenantProfileConfiguration::getRuleEngineExceptionsTtlDays);
ttl = TimeUnit.DAYS.toSeconds(ttl); ttl = TimeUnit.DAYS.toSeconds(ttl);
tsService.saveAndNotifyInternal(tenantId, getQueueStatsId(tenantId, queueName), Collections.singletonList(tsKv), ttl, CALLBACK); tsService.saveTimeseriesInternal(TimeseriesSaveRequest.builder()
.tenantId(tenantId)
.entityId(getQueueStatsId(tenantId, queueName))
.entry(tsKv)
.ttl(ttl)
.callback(CALLBACK)
.build());
} catch (Exception e2) { } catch (Exception e2) {
if (!"Asset is referencing to non-existent tenant!".equalsIgnoreCase(e2.getMessage())) { if (!"Asset is referencing to non-existent tenant!".equalsIgnoreCase(e2.getMessage())) {
log.debug("[{}] Failed to store the statistics", tenantId, e2); log.debug("[{}] Failed to store the statistics", tenantId, e2);

1
application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/AssetProfileExportService.java

@ -34,7 +34,6 @@ public class AssetProfileExportService extends BaseEntityExportService<AssetProf
assetProfile.setDefaultDashboardId(getExternalIdOrElseInternal(ctx, assetProfile.getDefaultDashboardId())); assetProfile.setDefaultDashboardId(getExternalIdOrElseInternal(ctx, assetProfile.getDefaultDashboardId()));
assetProfile.setDefaultRuleChainId(getExternalIdOrElseInternal(ctx, assetProfile.getDefaultRuleChainId())); assetProfile.setDefaultRuleChainId(getExternalIdOrElseInternal(ctx, assetProfile.getDefaultRuleChainId()));
assetProfile.setDefaultEdgeRuleChainId(getExternalIdOrElseInternal(ctx, assetProfile.getDefaultEdgeRuleChainId())); assetProfile.setDefaultEdgeRuleChainId(getExternalIdOrElseInternal(ctx, assetProfile.getDefaultEdgeRuleChainId()));
imageService.inlineImage(assetProfile);
} }
@Override @Override

3
application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DefaultEntityExportService.java

@ -32,7 +32,6 @@ import org.thingsboard.server.common.data.sync.ie.AttributeExportData;
import org.thingsboard.server.common.data.sync.ie.EntityExportData; import org.thingsboard.server.common.data.sync.ie.EntityExportData;
import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.relation.RelationDao; import org.thingsboard.server.dao.relation.RelationDao;
import org.thingsboard.server.dao.resource.ImageService;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.sync.ie.exporting.EntityExportService; import org.thingsboard.server.service.sync.ie.exporting.EntityExportService;
import org.thingsboard.server.service.sync.ie.exporting.ExportableEntitiesService; import org.thingsboard.server.service.sync.ie.exporting.ExportableEntitiesService;
@ -60,8 +59,6 @@ public class DefaultEntityExportService<I extends EntityId, E extends Exportable
private RelationDao relationDao; private RelationDao relationDao;
@Autowired @Autowired
private AttributesService attributesService; private AttributesService attributesService;
@Autowired
protected ImageService imageService;
@Override @Override
public final D getExportData(EntitiesExportCtx<?> ctx, I entityId) throws ThingsboardException { public final D getExportData(EntitiesExportCtx<?> ctx, I entityId) throws ThingsboardException {

1
application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DeviceProfileExportService.java

@ -34,7 +34,6 @@ public class DeviceProfileExportService extends BaseEntityExportService<DevicePr
deviceProfile.setDefaultDashboardId(getExternalIdOrElseInternal(ctx, deviceProfile.getDefaultDashboardId())); deviceProfile.setDefaultDashboardId(getExternalIdOrElseInternal(ctx, deviceProfile.getDefaultDashboardId()));
deviceProfile.setDefaultRuleChainId(getExternalIdOrElseInternal(ctx, deviceProfile.getDefaultRuleChainId())); deviceProfile.setDefaultRuleChainId(getExternalIdOrElseInternal(ctx, deviceProfile.getDefaultRuleChainId()));
deviceProfile.setDefaultEdgeRuleChainId(getExternalIdOrElseInternal(ctx, deviceProfile.getDefaultEdgeRuleChainId())); deviceProfile.setDefaultEdgeRuleChainId(getExternalIdOrElseInternal(ctx, deviceProfile.getDefaultEdgeRuleChainId()));
imageService.inlineImage(deviceProfile);
} }
@Override @Override

1
application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/WidgetsBundleExportService.java

@ -40,7 +40,6 @@ public class WidgetsBundleExportService extends BaseEntityExportService<WidgetsB
if (widgetsBundle.getTenantId() == null || widgetsBundle.getTenantId().isNullUid()) { if (widgetsBundle.getTenantId() == null || widgetsBundle.getTenantId().isNullUid()) {
throw new IllegalArgumentException("Export of system Widget Bundles is not allowed"); throw new IllegalArgumentException("Export of system Widget Bundles is not allowed");
} }
imageService.inlineImage(widgetsBundle);
List<String> fqns = widgetTypeService.findWidgetFqnsByWidgetsBundleId(ctx.getTenantId(), widgetsBundle.getId()); List<String> fqns = widgetTypeService.findWidgetFqnsByWidgetsBundleId(ctx.getTenantId(), widgetsBundle.getId());
exportData.setFqns(fqns); exportData.setFqns(fqns);

75
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java

@ -32,6 +32,8 @@ import org.springframework.security.core.context.SecurityContextHolder;
import org.thingsboard.common.util.DonAsynchron; import org.thingsboard.common.util.DonAsynchron;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.rule.engine.api.AttributesSaveRequest;
import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
import org.thingsboard.server.common.adaptor.JsonConverter; import org.thingsboard.server.common.adaptor.JsonConverter;
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;
@ -206,20 +208,27 @@ public abstract class AbstractBulkImportService<E extends HasId<? extends Entity
accessValidator.validateEntityAndCallback(user, Operation.WRITE_TELEMETRY, entity.getId(), (result, tenantId, entityId) -> { accessValidator.validateEntityAndCallback(user, Operation.WRITE_TELEMETRY, entity.getId(), (result, tenantId, entityId) -> {
TenantProfile tenantProfile = tenantProfileCache.get(tenantId); TenantProfile tenantProfile = tenantProfileCache.get(tenantId);
long tenantTtl = TimeUnit.DAYS.toSeconds(((DefaultTenantProfileConfiguration) tenantProfile.getProfileData().getConfiguration()).getDefaultStorageTtlDays()); long tenantTtl = TimeUnit.DAYS.toSeconds(((DefaultTenantProfileConfiguration) tenantProfile.getProfileData().getConfiguration()).getDefaultStorageTtlDays());
tsSubscriptionService.saveAndNotify(tenantId, user.getCustomerId(), entityId, timeseries, tenantTtl, new FutureCallback<Void>() { tsSubscriptionService.saveTimeseries(TimeseriesSaveRequest.builder()
@Override .tenantId(tenantId)
public void onSuccess(@Nullable Void tmp) { .customerId(user.getCustomerId())
entityActionService.logEntityAction(user, (UUIDBased & EntityId) entityId, null, null, .entityId(entityId)
ActionType.TIMESERIES_UPDATED, null, timeseries); .entries(timeseries)
} .ttl(tenantTtl)
.callback(new FutureCallback<>() {
@Override @Override
public void onFailure(Throwable t) { public void onSuccess(@Nullable Void tmp) {
entityActionService.logEntityAction(user, (UUIDBased & EntityId) entityId, null, null, entityActionService.logEntityAction(user, (UUIDBased & EntityId) entityId, null, null,
ActionType.TIMESERIES_UPDATED, BaseController.toException(t), timeseries); ActionType.TIMESERIES_UPDATED, null, timeseries);
throw new RuntimeException(t); }
}
}); @Override
public void onFailure(Throwable t) {
entityActionService.logEntityAction(user, (UUIDBased & EntityId) entityId, null, null,
ActionType.TIMESERIES_UPDATED, BaseController.toException(t), timeseries);
throw new RuntimeException(t);
}
})
.build());
}); });
} }
@ -229,23 +238,27 @@ public abstract class AbstractBulkImportService<E extends HasId<? extends Entity
List<AttributeKvEntry> attributes = new ArrayList<>(JsonConverter.convertToAttributes(kvsEntry.getValue())); List<AttributeKvEntry> attributes = new ArrayList<>(JsonConverter.convertToAttributes(kvsEntry.getValue()));
accessValidator.validateEntityAndCallback(user, Operation.WRITE_ATTRIBUTES, entity.getId(), (result, tenantId, entityId) -> { accessValidator.validateEntityAndCallback(user, Operation.WRITE_ATTRIBUTES, entity.getId(), (result, tenantId, entityId) -> {
tsSubscriptionService.saveAndNotify(tenantId, entityId, AttributeScope.valueOf(scope), attributes, new FutureCallback<>() { tsSubscriptionService.saveAttributes(AttributesSaveRequest.builder()
.tenantId(tenantId)
@Override .entityId(entityId)
public void onSuccess(Void unused) { .scope(AttributeScope.valueOf(scope))
entityActionService.logEntityAction(user, (UUIDBased & EntityId) entityId, null, .entries(attributes)
null, ActionType.ATTRIBUTES_UPDATED, null, AttributeScope.valueOf(scope), attributes); .callback(new FutureCallback<>() {
} @Override
public void onSuccess(Void unused) {
@Override entityActionService.logEntityAction(user, (UUIDBased & EntityId) entityId, null,
public void onFailure(Throwable throwable) { null, ActionType.ATTRIBUTES_UPDATED, null, AttributeScope.valueOf(scope), attributes);
entityActionService.logEntityAction(user, (UUIDBased & EntityId) entityId, null, }
null, ActionType.ATTRIBUTES_UPDATED, BaseController.toException(throwable),
AttributeScope.valueOf(scope), attributes); @Override
throw new RuntimeException(throwable); public void onFailure(Throwable throwable) {
} entityActionService.logEntityAction(user, (UUIDBased & EntityId) entityId, null,
null, ActionType.ATTRIBUTES_UPDATED, BaseController.toException(throwable),
}); AttributeScope.valueOf(scope), attributes);
throw new RuntimeException(throwable);
}
})
.build());
}); });
} }

25
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/BaseEntityImportService.java

@ -24,6 +24,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.AttributesSaveRequest;
import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ExportableEntity; import org.thingsboard.server.common.data.ExportableEntity;
@ -257,16 +258,22 @@ public abstract class BaseEntityImportService<I extends EntityId, E extends Expo
}) })
.collect(Collectors.toList()); .collect(Collectors.toList());
// fixme: attributes are saved outside the transaction // fixme: attributes are saved outside the transaction
tsSubService.saveAndNotify(user.getTenantId(), entity.getId(), scope, attributeKvEntries, new FutureCallback<Void>() { tsSubService.saveAttributes(AttributesSaveRequest.builder()
@Override .tenantId(user.getTenantId())
public void onSuccess(@Nullable Void unused) { .entityId(entity.getId())
} .scope(scope)
.entries(attributeKvEntries)
.callback(new FutureCallback<>() {
@Override
public void onSuccess(@Nullable Void unused) {
}
@Override @Override
public void onFailure(Throwable thr) { public void onFailure(Throwable thr) {
log.error("Failed to import attributes for {} {}", entity.getId().getEntityType(), entity.getId(), thr); log.error("Failed to import attributes for {} {}", entity.getId().getEntityType(), entity.getId(), thr);
} }
}); })
.build());
}); });
}); });
} }

13
application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java

@ -27,6 +27,7 @@ import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.rule.engine.api.SmsService; import org.thingsboard.rule.engine.api.SmsService;
import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.ApiUsageState;
import org.thingsboard.server.common.data.FeaturesInfo; import org.thingsboard.server.common.data.FeaturesInfo;
@ -71,9 +72,9 @@ import static org.thingsboard.common.util.SystemUtil.getTotalMemory;
@Slf4j @Slf4j
public class DefaultSystemInfoService extends TbApplicationEventListener<PartitionChangeEvent> implements SystemInfoService { public class DefaultSystemInfoService extends TbApplicationEventListener<PartitionChangeEvent> implements SystemInfoService {
public static final FutureCallback<Integer> CALLBACK = new FutureCallback<>() { public static final FutureCallback<Void> CALLBACK = new FutureCallback<>() {
@Override @Override
public void onSuccess(@Nullable Integer result) { public void onSuccess(@Nullable Void result) {
} }
@Override @Override
@ -200,7 +201,13 @@ public class DefaultSystemInfoService extends TbApplicationEventListener<Partiti
private void doSave(List<TsKvEntry> telemetry) { private void doSave(List<TsKvEntry> telemetry) {
ApiUsageState apiUsageState = apiUsageStateClient.getApiUsageState(TenantId.SYS_TENANT_ID); ApiUsageState apiUsageState = apiUsageStateClient.getApiUsageState(TenantId.SYS_TENANT_ID);
telemetryService.saveAndNotifyInternal(TenantId.SYS_TENANT_ID, apiUsageState.getId(), telemetry, systemInfoTtlSeconds, CALLBACK); telemetryService.saveTimeseriesInternal(TimeseriesSaveRequest.builder()
.tenantId(TenantId.SYS_TENANT_ID)
.entityId(apiUsageState.getId())
.entries(telemetry)
.ttl(systemInfoTtlSeconds)
.callback(CALLBACK)
.build());
} }
private List<SystemInfoData> getSystemData(ServiceInfo serviceInfo) { private List<SystemInfoData> getSystemData(ServiceInfo serviceInfo) {

446
application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java

@ -19,29 +19,28 @@ 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 com.google.common.util.concurrent.SettableFuture;
import jakarta.annotation.Nullable; import jakarta.annotation.Nullable;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy; import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.DonAsynchron;
import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.rule.engine.api.AttributesDeleteRequest;
import org.thingsboard.rule.engine.api.AttributesSaveRequest;
import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
import org.thingsboard.rule.engine.api.TimeseriesDeleteRequest;
import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageRecordKey;
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.EntityView; import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
import org.thingsboard.server.common.data.kv.DoubleDataEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.kv.TsKvLatestRemovingResult; import org.thingsboard.server.common.data.kv.TsKvLatestRemovingResult;
import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TbCallback;
@ -54,8 +53,6 @@ import org.thingsboard.server.service.entitiy.entityview.TbEntityViewService;
import org.thingsboard.server.service.subscription.TbSubscriptionUtils; import org.thingsboard.server.service.subscription.TbSubscriptionUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -64,13 +61,14 @@ import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.function.Consumer;
/** /**
* Created by ashvayka on 27.03.18. * Created by ashvayka on 27.03.18.
*/ */
@Service @Service
@Slf4j @Slf4j
public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionService implements TelemetrySubscriptionService { public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionService implements TelemetrySubscriptionService, RuleEngineTelemetryService {
private final AttributesService attrService; private final AttributesService attrService;
private final TimeseriesService tsService; private final TimeseriesService tsService;
@ -115,76 +113,92 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
} }
@Override @Override
public ListenableFuture<Void> saveAndNotify(TenantId tenantId, EntityId entityId, TsKvEntry ts) { public void saveTimeseries(TimeseriesSaveRequest request) {
SettableFuture<Void> future = SettableFuture.create(); TenantId tenantId = request.getTenantId();
saveAndNotify(tenantId, entityId, Collections.singletonList(ts), new VoidFutureCallback(future)); EntityId entityId = request.getEntityId();
return future; checkInternalEntity(entityId);
boolean sysTenant = TenantId.SYS_TENANT_ID.equals(tenantId) || tenantId == null;
if (sysTenant || request.isOnlyLatest() || apiUsageStateService.getApiUsageState(tenantId).isDbStorageEnabled()) {
KvUtils.validate(request.getEntries(), valueNoXssValidation);
ListenableFuture<Integer> future = saveTimeseriesInternal(request);
if (!request.isOnlyLatest()) {
FutureCallback<Integer> callback = getApiUsageCallback(tenantId, request.getCustomerId(), sysTenant, request.getCallback());
Futures.addCallback(future, callback, tsCallBackExecutor);
}
} else {
request.getCallback().onFailure(new RuntimeException("DB storage writes are disabled due to API limits!"));
}
} }
@Override @Override
public void saveAndNotify(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, FutureCallback<Void> callback) { public ListenableFuture<Integer> saveTimeseriesInternal(TimeseriesSaveRequest request) {
saveAndNotify(tenantId, null, entityId, ts, 0L, callback); TenantId tenantId = request.getTenantId();
} EntityId entityId = request.getEntityId();
ListenableFuture<Integer> saveFuture;
if (request.isOnlyLatest()) {
saveFuture = Futures.transform(tsService.saveLatest(tenantId, entityId, request.getEntries()), result -> 0, MoreExecutors.directExecutor());
} else if (request.isSaveLatest()) {
saveFuture = tsService.save(tenantId, entityId, request.getEntries(), request.getTtl());
} else {
saveFuture = tsService.saveWithoutLatest(tenantId, entityId, request.getEntries(), request.getTtl());
}
@Override addMainCallback(saveFuture, request.getCallback());
public void saveAndNotify(TenantId tenantId, CustomerId customerId, EntityId entityId, List<TsKvEntry> ts, long ttl, FutureCallback<Void> callback) { addWsCallback(saveFuture, success -> onTimeSeriesUpdate(tenantId, entityId, request.getEntries()));
doSaveAndNotify(tenantId, customerId, entityId, ts, ttl, callback, true); if (request.isSaveLatest() && !request.isOnlyLatest()) {
addEntityViewCallback(tenantId, entityId, request.getEntries());
}
return saveFuture;
} }
@Override @Override
public void saveWithoutLatestAndNotify(TenantId tenantId, CustomerId customerId, EntityId entityId, List<TsKvEntry> ts, long ttl, FutureCallback<Void> callback) { public void saveAttributes(AttributesSaveRequest request) {
doSaveAndNotify(tenantId, customerId, entityId, ts, ttl, callback, false); checkInternalEntity(request.getEntityId());
saveAttributesInternal(request);
} }
private void doSaveAndNotify(TenantId tenantId, CustomerId customerId, EntityId entityId, List<TsKvEntry> ts, long ttl, FutureCallback<Void> callback, boolean saveLatest) { @Override
checkInternalEntity(entityId); public void saveAttributesInternal(AttributesSaveRequest request) {
boolean sysTenant = TenantId.SYS_TENANT_ID.equals(tenantId) || tenantId == null; log.trace("Executing saveInternal [{}]", request);
if (sysTenant || apiUsageStateService.getApiUsageState(tenantId).isDbStorageEnabled()) { ListenableFuture<List<Long>> saveFuture = attrService.save(request.getTenantId(), request.getEntityId(), request.getScope(), request.getEntries());
KvUtils.validate(ts, valueNoXssValidation); addMainCallback(saveFuture, request.getCallback());
if (saveLatest) { addWsCallback(saveFuture, success -> onAttributesUpdate(request.getTenantId(), request.getEntityId(), request.getScope().name(), request.getEntries(), request.isNotifyDevice()));
saveAndNotifyInternal(tenantId, entityId, ts, ttl, getCallback(tenantId, customerId, sysTenant, callback));
} else {
saveWithoutLatestAndNotifyInternal(tenantId, entityId, ts, ttl, getCallback(tenantId, customerId, sysTenant, callback));
}
} else {
callback.onFailure(new RuntimeException("DB storage writes are disabled due to API limits!"));
}
} }
private FutureCallback<Integer> getCallback(TenantId tenantId, CustomerId customerId, boolean sysTenant, FutureCallback<Void> callback) { @Override
return new FutureCallback<>() { public void deleteAttributes(AttributesDeleteRequest request) {
@Override checkInternalEntity(request.getEntityId());
public void onSuccess(Integer result) { deleteAttributesInternal(request);
if (!sysTenant && result != null && result > 0) {
apiUsageClient.report(tenantId, customerId, ApiUsageRecordKey.STORAGE_DP_COUNT, result);
}
callback.onSuccess(null);
}
@Override
public void onFailure(Throwable t) {
callback.onFailure(t);
}
};
} }
@Override @Override
public void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, FutureCallback<Integer> callback) { public void deleteAttributesInternal(AttributesDeleteRequest request) {
saveAndNotifyInternal(tenantId, entityId, ts, 0L, callback); ListenableFuture<List<String>> deleteFuture = attrService.removeAll(request.getTenantId(), request.getEntityId(), request.getScope(), request.getKeys());
addMainCallback(deleteFuture, request.getCallback());
addWsCallback(deleteFuture, success -> onAttributesDelete(request.getTenantId(), request.getEntityId(), request.getScope().name(), request.getKeys(), request.isNotifyDevice()));
} }
@Override @Override
public void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, long ttl, FutureCallback<Integer> callback) { public void deleteTimeseries(TimeseriesDeleteRequest request) {
ListenableFuture<Integer> saveFuture = tsService.save(tenantId, entityId, ts, ttl); checkInternalEntity(request.getEntityId());
addMainCallback(saveFuture, callback); deleteTimeseriesInternal(request);
addWsCallback(saveFuture, success -> onTimeSeriesUpdate(tenantId, entityId, ts));
addEntityViewCallback(tenantId, entityId, ts);
} }
private void saveWithoutLatestAndNotifyInternal(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, long ttl, FutureCallback<Integer> callback) { @Override
ListenableFuture<Integer> saveFuture = tsService.saveWithoutLatest(tenantId, entityId, ts, ttl); public void deleteTimeseriesInternal(TimeseriesDeleteRequest request) {
addMainCallback(saveFuture, callback); if (CollectionUtils.isNotEmpty(request.getKeys())) {
addWsCallback(saveFuture, success -> onTimeSeriesUpdate(tenantId, entityId, ts)); ListenableFuture<List<TsKvLatestRemovingResult>> deleteFuture;
if (request.getDeleteHistoryQueries() == null) {
deleteFuture = tsService.removeLatest(request.getTenantId(), request.getEntityId(), request.getKeys());
} else {
deleteFuture = tsService.remove(request.getTenantId(), request.getEntityId(), request.getDeleteHistoryQueries());
addWsCallback(deleteFuture, result -> onTimeSeriesDelete(request.getTenantId(), request.getEntityId(), request.getKeys(), result));
}
addMainCallback(deleteFuture, __ -> request.getCallback().onSuccess(request.getKeys()), request.getCallback()::onFailure);
} else {
ListenableFuture<List<String>> deleteFuture = tsService.removeAllLatest(request.getTenantId(), request.getEntityId());
addMainCallback(deleteFuture, request.getCallback()::onSuccess, request.getCallback()::onFailure);
}
} }
private void addEntityViewCallback(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts) { private void addEntityViewCallback(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts) {
@ -214,15 +228,21 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
} }
} }
if (!entityViewLatest.isEmpty()) { if (!entityViewLatest.isEmpty()) {
saveLatestAndNotify(tenantId, entityView.getId(), entityViewLatest, new FutureCallback<>() { saveTimeseries(TimeseriesSaveRequest.builder()
@Override .tenantId(tenantId)
public void onSuccess(@Nullable Void tmp) { .entityId(entityView.getId())
} .entries(entityViewLatest)
.onlyLatest(true)
@Override .callback(new FutureCallback<>() {
public void onFailure(Throwable t) { @Override
} public void onSuccess(@Nullable Void tmp) {}
});
@Override
public void onFailure(Throwable t) {
log.error("[{}][{}] Failed to save entity view latest timeseries: {}", tenantId, entityView.getId(), entityViewLatest, t);
}
})
.build());
} }
} }
} }
@ -236,233 +256,6 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
} }
} }
@Override
public void saveAndNotify(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, FutureCallback<Void> callback) {
saveAndNotify(tenantId, entityId, scope, attributes, true, callback);
}
@Override
public void saveAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, List<AttributeKvEntry> attributes, FutureCallback<Void> callback) {
saveAndNotify(tenantId, entityId, scope, attributes, true, callback);
}
@Override
public void saveAndNotify(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, boolean notifyDevice, FutureCallback<Void> callback) {
checkInternalEntity(entityId);
saveAndNotifyInternal(tenantId, entityId, scope, attributes, notifyDevice, callback);
}
@Override
public void saveAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, List<AttributeKvEntry> attributes, boolean notifyDevice, FutureCallback<Void> callback) {
checkInternalEntity(entityId);
saveAndNotifyInternal(tenantId, entityId, scope, attributes, notifyDevice, callback);
}
@Override
public void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, boolean notifyDevice, FutureCallback<Void> callback) {
ListenableFuture<List<Long>> saveFuture = attrService.save(tenantId, entityId, scope, attributes);
addVoidCallback(saveFuture, callback);
addWsCallback(saveFuture, success -> onAttributesUpdate(tenantId, entityId, scope, attributes, notifyDevice));
}
@Override
public void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, AttributeScope scope, List<AttributeKvEntry> attributes, boolean notifyDevice, FutureCallback<Void> callback) {
ListenableFuture<List<Long>> saveFuture = attrService.save(tenantId, entityId, scope, attributes);
addVoidCallback(saveFuture, callback);
addWsCallback(saveFuture, success -> onAttributesUpdate(tenantId, entityId, scope.name(), attributes, notifyDevice));
}
@Override
public void saveLatestAndNotify(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, FutureCallback<Void> callback) {
checkInternalEntity(entityId);
saveLatestAndNotifyInternal(tenantId, entityId, ts, callback);
}
@Override
public void saveLatestAndNotifyInternal(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, FutureCallback<Void> callback) {
ListenableFuture<List<Long>> saveFuture = tsService.saveLatest(tenantId, entityId, ts);
addVoidCallback(saveFuture, callback);
addWsCallback(saveFuture, success -> onTimeSeriesUpdate(tenantId, entityId, ts));
}
@Override
public void deleteAndNotify(TenantId tenantId, EntityId entityId, String scope, List<String> keys, FutureCallback<Void> callback) {
checkInternalEntity(entityId);
deleteAndNotifyInternal(tenantId, entityId, scope, keys, false, callback);
}
@Override
public void deleteAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, List<String> keys, FutureCallback<Void> callback) {
checkInternalEntity(entityId);
deleteAndNotifyInternal(tenantId, entityId, scope, keys, false, callback);
}
@Override
public void deleteAndNotify(TenantId tenantId, EntityId entityId, String scope, List<String> keys, boolean notifyDevice, FutureCallback<Void> callback) {
checkInternalEntity(entityId);
deleteAndNotifyInternal(tenantId, entityId, scope, keys, notifyDevice, callback);
}
@Override
public void deleteAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, List<String> keys, boolean notifyDevice, FutureCallback<Void> callback) {
checkInternalEntity(entityId);
deleteAndNotifyInternal(tenantId, entityId, scope, keys, notifyDevice, callback);
}
@Override
public void deleteAndNotifyInternal(TenantId tenantId, EntityId entityId, String scope, List<String> keys, boolean notifyDevice, FutureCallback<Void> callback) {
ListenableFuture<List<String>> deleteFuture = attrService.removeAll(tenantId, entityId, scope, keys);
addVoidCallback(deleteFuture, callback);
addWsCallback(deleteFuture, success -> onAttributesDelete(tenantId, entityId, scope, keys, notifyDevice));
}
@Override
public void deleteAndNotifyInternal(TenantId tenantId, EntityId entityId, AttributeScope scope, List<String> keys, boolean notifyDevice, FutureCallback<Void> callback) {
ListenableFuture<List<String>> deleteFuture = attrService.removeAll(tenantId, entityId, scope, keys);
addVoidCallback(deleteFuture, callback);
addWsCallback(deleteFuture, success -> onAttributesDelete(tenantId, entityId, scope.name(), keys, notifyDevice));
}
@Override
public void deleteLatest(TenantId tenantId, EntityId entityId, List<String> keys, FutureCallback<Void> callback) {
checkInternalEntity(entityId);
deleteLatestInternal(tenantId, entityId, keys, callback);
}
@Override
public void deleteLatestInternal(TenantId tenantId, EntityId entityId, List<String> keys, FutureCallback<Void> callback) {
ListenableFuture<List<TsKvLatestRemovingResult>> deleteFuture = tsService.removeLatest(tenantId, entityId, keys);
addVoidCallback(deleteFuture, callback);
}
@Override
public void deleteAllLatest(TenantId tenantId, EntityId entityId, FutureCallback<Collection<String>> callback) {
ListenableFuture<Collection<String>> deleteFuture = tsService.removeAllLatest(tenantId, entityId);
Futures.addCallback(deleteFuture, new FutureCallback<Collection<String>>() {
@Override
public void onSuccess(@Nullable Collection<String> result) {
callback.onSuccess(result);
}
@Override
public void onFailure(Throwable t) {
callback.onFailure(t);
}
}, tsCallBackExecutor);
}
@Override
public void deleteTimeseriesAndNotify(TenantId tenantId, EntityId entityId, List<String> keys, List<DeleteTsKvQuery> deleteTsKvQueries, FutureCallback<Void> callback) {
ListenableFuture<List<TsKvLatestRemovingResult>> deleteFuture = tsService.remove(tenantId, entityId, deleteTsKvQueries);
addVoidCallback(deleteFuture, callback);
addWsCallback(deleteFuture, list -> onTimeSeriesDelete(tenantId, entityId, keys, list));
}
@Override
public void saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, long value, FutureCallback<Void> callback) {
saveAndNotify(tenantId, entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new LongDataEntry(key, value)
, System.currentTimeMillis())), callback);
}
@Override
public void saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, long value, FutureCallback<Void> callback) {
saveAndNotify(tenantId, entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new LongDataEntry(key, value)
, System.currentTimeMillis())), callback);
}
@Override
public void saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, String value, FutureCallback<Void> callback) {
saveAndNotify(tenantId, entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry(key, value)
, System.currentTimeMillis())), callback);
}
@Override
public void saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, String value, FutureCallback<Void> callback) {
saveAndNotify(tenantId, entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry(key, value)
, System.currentTimeMillis())), callback);
}
@Override
public void saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, double value, FutureCallback<Void> callback) {
saveAndNotify(tenantId, entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new DoubleDataEntry(key, value)
, System.currentTimeMillis())), callback);
}
@Override
public void saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, double value, FutureCallback<Void> callback) {
saveAndNotify(tenantId, entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new DoubleDataEntry(key, value)
, System.currentTimeMillis())), callback);
}
@Override
public void saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, boolean value, FutureCallback<Void> callback) {
saveAndNotify(tenantId, entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new BooleanDataEntry(key, value)
, System.currentTimeMillis())), callback);
}
@Override
public void saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, boolean value, FutureCallback<Void> callback) {
saveAndNotify(tenantId, entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new BooleanDataEntry(key, value)
, System.currentTimeMillis())), callback);
}
@Override
public ListenableFuture<Void> saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, long value) {
SettableFuture<Void> future = SettableFuture.create();
saveAttrAndNotify(tenantId, entityId, scope, key, value, new VoidFutureCallback(future));
return future;
}
@Override
public ListenableFuture<Void> saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, long value) {
SettableFuture<Void> future = SettableFuture.create();
saveAttrAndNotify(tenantId, entityId, scope, key, value, new VoidFutureCallback(future));
return future;
}
@Override
public ListenableFuture<Void> saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, String value) {
SettableFuture<Void> future = SettableFuture.create();
saveAttrAndNotify(tenantId, entityId, scope, key, value, new VoidFutureCallback(future));
return future;
}
@Override
public ListenableFuture<Void> saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, String value) {
SettableFuture<Void> future = SettableFuture.create();
saveAttrAndNotify(tenantId, entityId, scope, key, value, new VoidFutureCallback(future));
return future;
}
@Override
public ListenableFuture<Void> saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, double value) {
SettableFuture<Void> future = SettableFuture.create();
saveAttrAndNotify(tenantId, entityId, scope, key, value, new VoidFutureCallback(future));
return future;
}
@Override
public ListenableFuture<Void> saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, double value) {
SettableFuture<Void> future = SettableFuture.create();
saveAttrAndNotify(tenantId, entityId, scope, key, value, new VoidFutureCallback(future));
return future;
}
@Override
public ListenableFuture<Void> saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, boolean value) {
SettableFuture<Void> future = SettableFuture.create();
saveAttrAndNotify(tenantId, entityId, scope, key, value, new VoidFutureCallback(future));
return future;
}
@Override
public ListenableFuture<Void> saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, boolean value) {
SettableFuture<Void> future = SettableFuture.create();
saveAttrAndNotify(tenantId, entityId, scope, key, value, new VoidFutureCallback(future));
return future;
}
private void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, boolean notifyDevice) { private void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, boolean notifyDevice) {
forwardToSubscriptionManagerService(tenantId, entityId, subscriptionManagerService -> { forwardToSubscriptionManagerService(tenantId, entityId, subscriptionManagerService -> {
subscriptionManagerService.onAttributesUpdate(tenantId, entityId, scope, attributes, notifyDevice, TbCallback.EMPTY); subscriptionManagerService.onAttributesUpdate(tenantId, entityId, scope, attributes, notifyDevice, TbCallback.EMPTY);
@ -509,33 +302,13 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
}); });
} }
private <S> void addVoidCallback(ListenableFuture<S> saveFuture, final FutureCallback<Void> callback) { private <S> void addMainCallback(ListenableFuture<S> saveFuture, final FutureCallback<Void> callback) {
if (callback == null) return; if (callback == null) return;
Futures.addCallback(saveFuture, new FutureCallback<S>() { addMainCallback(saveFuture, result -> callback.onSuccess(null), callback::onFailure);
@Override
public void onSuccess(@Nullable S result) {
callback.onSuccess(null);
}
@Override
public void onFailure(Throwable t) {
callback.onFailure(t);
}
}, tsCallBackExecutor);
} }
private <S> void addMainCallback(ListenableFuture<S> saveFuture, final FutureCallback<S> callback) { private <S> void addMainCallback(ListenableFuture<S> saveFuture, Consumer<S> onSuccess, Consumer<Throwable> onFailure) {
Futures.addCallback(saveFuture, new FutureCallback<S>() { DonAsynchron.withCallback(saveFuture, onSuccess, onFailure, tsCallBackExecutor);
@Override
public void onSuccess(@Nullable S result) {
callback.onSuccess(result);
}
@Override
public void onFailure(Throwable t) {
callback.onFailure(t);
}
}, tsCallBackExecutor);
} }
private void checkInternalEntity(EntityId entityId) { private void checkInternalEntity(EntityId entityId) {
@ -544,22 +317,21 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
} }
} }
private static class VoidFutureCallback implements FutureCallback<Void> { private FutureCallback<Integer> getApiUsageCallback(TenantId tenantId, CustomerId customerId, boolean sysTenant, FutureCallback<Void> callback) {
private final SettableFuture<Void> future; return new FutureCallback<>() {
@Override
public VoidFutureCallback(SettableFuture<Void> future) { public void onSuccess(Integer result) {
this.future = future; if (!sysTenant && result != null && result > 0) {
} apiUsageClient.report(tenantId, customerId, ApiUsageRecordKey.STORAGE_DP_COUNT, result);
}
@Override callback.onSuccess(null);
public void onSuccess(Void result) { }
future.set(null);
}
@Override @Override
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
future.setException(t); callback.onFailure(t);
} }
};
} }
} }

31
application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java

@ -15,37 +15,24 @@
*/ */
package org.thingsboard.server.service.telemetry; package org.thingsboard.server.service.telemetry;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.rule.engine.api.AttributesDeleteRequest;
import org.thingsboard.rule.engine.api.AttributesSaveRequest;
import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.rule.engine.api.TimeseriesDeleteRequest;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import java.util.List;
/** /**
* Created by ashvayka on 27.03.18. * Created by ashvayka on 27.03.18.
*/ */
public interface InternalTelemetryService extends RuleEngineTelemetryService { public interface InternalTelemetryService extends RuleEngineTelemetryService {
void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, FutureCallback<Integer> callback); ListenableFuture<Integer> saveTimeseriesInternal(TimeseriesSaveRequest request);
void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, long ttl, FutureCallback<Integer> callback);
@Deprecated(since = "3.7.0")
void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, boolean notifyDevice, FutureCallback<Void> callback);
void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, AttributeScope scope, List<AttributeKvEntry> attributes, boolean notifyDevice, FutureCallback<Void> callback);
void saveLatestAndNotifyInternal(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, FutureCallback<Void> callback);
@Deprecated(since = "3.7.0") void saveAttributesInternal(AttributesSaveRequest request);
void deleteAndNotifyInternal(TenantId tenantId, EntityId entityId, String scope, List<String> keys, boolean notifyDevice, FutureCallback<Void> callback);
void deleteAndNotifyInternal(TenantId tenantId, EntityId entityId, AttributeScope scope, List<String> keys, boolean notifyDevice, FutureCallback<Void> callback); void deleteTimeseriesInternal(TimeseriesDeleteRequest request);
void deleteLatestInternal(TenantId tenantId, EntityId entityId, List<String> keys, FutureCallback<Void> callback); void deleteAttributesInternal(AttributesDeleteRequest request);
} }

9
application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java

@ -362,7 +362,14 @@ public class DefaultTransportApiService implements TransportApiService {
DeviceId deviceId = device.getId(); DeviceId deviceId = device.getId();
JsonNode entityNode = JacksonUtil.valueToTree(device); JsonNode entityNode = JacksonUtil.valueToTree(device);
TbMsg tbMsg = TbMsg.newMsg(TbMsgType.ENTITY_CREATED, deviceId, customerId, metaData, TbMsgDataType.JSON, JacksonUtil.toString(entityNode)); TbMsg tbMsg = TbMsg.newMsg()
.type(TbMsgType.ENTITY_CREATED)
.originator(deviceId)
.customerId(customerId)
.copyMetaData(metaData)
.dataType(TbMsgDataType.JSON)
.data(JacksonUtil.toString(entityNode))
.build();
tbClusterService.pushMsgToRuleEngine(tenantId, deviceId, tbMsg, null); tbClusterService.pushMsgToRuleEngine(tenantId, deviceId, tbMsg, null);
} else { } else {
JsonNode deviceAdditionalInfo = device.getAdditionalInfo(); JsonNode deviceAdditionalInfo = device.getAdditionalInfo();

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

@ -1302,6 +1302,30 @@ coap:
# - A value between 0 and <= 4: SingleNodeConnectionIdGenerator is used # - A value between 0 and <= 4: SingleNodeConnectionIdGenerator is used
# - A value that are > 4: MultiNodeConnectionIdGenerator is used # - A value that are > 4: MultiNodeConnectionIdGenerator is used
connection_id_length: "${COAP_DTLS_CONNECTION_ID_LENGTH:}" connection_id_length: "${COAP_DTLS_CONNECTION_ID_LENGTH:}"
# Specify the MTU (Maximum Transmission Unit).
# Should be used if LAN MTU is not used, e.g. if IP tunnels are used or if the client uses a smaller value than the LAN MTU.
# Default = 1024
# Minimum value = 64
# If set to 0 - LAN MTU is used.
max_transmission_unit: "${COAP_DTLS_MAX_TRANSMISSION_UNIT:1024}"
# DTLS maximum fragment length (RFC 6066, Section 4).
# Default = 1024
# Possible values: 512, 1024, 2048, 4096.
# If set to 0, the default maximum fragment size of 2^14 bytes (16,384 bytes) is used.
# Without this extension, TLS specifies a fixed maximum plaintext fragment length of 2^14 bytes.
# It may be desirable for constrained clients to negotiate a smaller maximum fragment length due to memory limitations or bandwidth limitations.
# In order to negotiate smaller maximum fragment lengths,
# clients MAY include an extension of type "max_fragment_length" in the (extended) client hello.
# The "extension_data" field of this extension SHALL contain:
# enum {
# 2^9(1) == 512,
# 2^10(2) == 1024,
# 2^11(3) == 2048,
# 2^12(4) == 4096,
# (255)
# } MaxFragmentLength;
# TLS already requires clients and servers to support fragmentation of handshake messages.
max_fragment_length: "${COAP_DTLS_MAX_FRAGMENT_LENGTH:1024}"
# Server DTLS credentials # Server DTLS credentials
credentials: credentials:
# Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore) # Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore)
@ -1422,11 +1446,11 @@ edges:
# Max number of high priority edge events per edge session. No persistence - stored in memory # Max number of high priority edge events per edge session. No persistence - stored in memory
max_high_priority_queue_size_per_session: "${EDGES_MAX_HIGH_PRIORITY_QUEUE_SIZE_PER_SESSION:10000}" max_high_priority_queue_size_per_session: "${EDGES_MAX_HIGH_PRIORITY_QUEUE_SIZE_PER_SESSION:10000}"
# Number of threads that are used to check DB for edge events # Number of threads that are used to check DB for edge events
scheduler_pool_size: "${EDGES_SCHEDULER_POOL_SIZE:1}" scheduler_pool_size: "${EDGES_SCHEDULER_POOL_SIZE:4}"
# Number of threads that are used to send downlink messages to edge over gRPC # Number of threads that are used to send downlink messages to edge over gRPC
send_scheduler_pool_size: "${EDGES_SEND_SCHEDULER_POOL_SIZE:1}" send_scheduler_pool_size: "${EDGES_SEND_SCHEDULER_POOL_SIZE:4}"
# Number of threads that are used to convert edge events from DB into downlink messages and send them for delivery # Number of threads that are used to convert edge events from DB into downlink messages and send them for delivery
grpc_callback_thread_pool_size: "${EDGES_GRPC_CALLBACK_POOL_SIZE:1}" grpc_callback_thread_pool_size: "${EDGES_GRPC_CALLBACK_POOL_SIZE:4}"
state: state:
# Persist state of edge (active, last connect, last disconnect) into timeseries or attributes tables. 'false' means to store edge state into attributes table # Persist state of edge (active, last connect, last disconnect) into timeseries or attributes tables. 'false' means to store edge state into attributes table
persistToTelemetry: "${EDGES_PERSIST_STATE_TO_TELEMETRY:false}" persistToTelemetry: "${EDGES_PERSIST_STATE_TO_TELEMETRY:false}"

23
application/src/test/java/org/thingsboard/server/actors/rule/DefaultTbContextTest.java

@ -928,15 +928,32 @@ class DefaultTbContextTest {
} }
private TbMsg getTbMsgWithCallback(TbMsgCallback callback) { private TbMsg getTbMsgWithCallback(TbMsgCallback callback) {
return TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, TENANT_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING, callback); return TbMsg.newMsg()
.type(TbMsgType.POST_TELEMETRY_REQUEST)
.originator(TENANT_ID)
.copyMetaData(TbMsgMetaData.EMPTY)
.data(TbMsg.EMPTY_STRING)
.callback(callback)
.build();
} }
private TbMsg getTbMsgWithQueueName() { private TbMsg getTbMsgWithQueueName() {
return TbMsg.newMsg(DataConstants.MAIN_QUEUE_NAME, TbMsgType.POST_TELEMETRY_REQUEST, TENANT_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); return TbMsg.newMsg()
.queueName(DataConstants.MAIN_QUEUE_NAME)
.type(TbMsgType.POST_TELEMETRY_REQUEST)
.originator(TENANT_ID)
.copyMetaData(TbMsgMetaData.EMPTY)
.data(TbMsg.EMPTY_STRING)
.build();
} }
private TbMsg getTbMsg() { private TbMsg getTbMsg() {
return TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, TENANT_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); return TbMsg.newMsg()
.type(TbMsgType.POST_TELEMETRY_REQUEST)
.originator(TENANT_ID)
.copyMetaData(TbMsgMetaData.EMPTY)
.data(TbMsg.EMPTY_STRING)
.build();
} }
private static long getUntilTime() { private static long getUntilTime() {

3
application/src/test/java/org/thingsboard/server/controller/AuthControllerTest.java

@ -83,6 +83,7 @@ public class AuthControllerTest extends AbstractControllerTest {
assertThat(user.getEmail()).isEqualTo(CUSTOMER_USER_EMAIL); assertThat(user.getEmail()).isEqualTo(CUSTOMER_USER_EMAIL);
user = getUser(customerUserId); user = getUser(customerUserId);
assertThat(user.getAdditionalInfo().get("userCredentialsEnabled").asBoolean()).isTrue(); assertThat(user.getAdditionalInfo().get("userCredentialsEnabled").asBoolean()).isTrue();
assertThat(user.getAdditionalInfo().get("userActivated").asBoolean()).isTrue();
assertThat(user.getAdditionalInfo().get("lastLoginTs").asLong()).isCloseTo(System.currentTimeMillis(), within(10000L)); assertThat(user.getAdditionalInfo().get("lastLoginTs").asLong()).isCloseTo(System.currentTimeMillis(), within(10000L));
} }
@ -243,6 +244,7 @@ public class AuthControllerTest extends AbstractControllerTest {
user.setAuthority(Authority.TENANT_ADMIN); user.setAuthority(Authority.TENANT_ADMIN);
user.setEmail("tenant-admin-2@thingsboard.org"); user.setEmail("tenant-admin-2@thingsboard.org");
user = doPost("/api/user", user, User.class); user = doPost("/api/user", user, User.class);
assertThat(getUser(user.getId()).getAdditionalInfo().get("userActivated").asBoolean()).isFalse();
UserCredentials userCredentials = userCredentialsDao.findByUserId(tenantId, user.getUuidId()); UserCredentials userCredentials = userCredentialsDao.findByUserId(tenantId, user.getUuidId());
assertThat(userCredentials.getActivateTokenExpTime()).isCloseTo(System.currentTimeMillis() + TimeUnit.HOURS.toMillis(ttl), Offset.offset(120000L)); assertThat(userCredentials.getActivateTokenExpTime()).isCloseTo(System.currentTimeMillis() + TimeUnit.HOURS.toMillis(ttl), Offset.offset(120000L));
@ -289,6 +291,7 @@ public class AuthControllerTest extends AbstractControllerTest {
doPost("/api/noauth/activate", JacksonUtil.newObjectNode() doPost("/api/noauth/activate", JacksonUtil.newObjectNode()
.put("activateToken", newActivationToken) .put("activateToken", newActivationToken)
.put("password", "wefewe")).andExpect(status().isOk()); .put("password", "wefewe")).andExpect(status().isOk());
assertThat(getUser(user.getId()).getAdditionalInfo().get("userActivated").asBoolean()).isTrue();
} }
@Test @Test

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

@ -24,6 +24,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.TestPropertySource;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.ImageDescriptor; import org.thingsboard.server.common.data.ImageDescriptor;
import org.thingsboard.server.common.data.ResourceExportData; import org.thingsboard.server.common.data.ResourceExportData;
import org.thingsboard.server.common.data.ResourceSubType; import org.thingsboard.server.common.data.ResourceSubType;
@ -321,6 +322,24 @@ public class ImageControllerTest extends AbstractControllerTest {
assertThat(systemParams.getMaxResourceSize()).isEqualTo(0); assertThat(systemParams.getMaxResourceSize()).isEqualTo(0);
} }
@Test
public void testInlineImages() throws Exception {
TbResourceInfo imageInfo = uploadImage(HttpMethod.POST, "/api/image", "my_png_image.png", "image/png", PNG_IMAGE);
DeviceProfile deviceProfile = createDeviceProfile("Test");
deviceProfile.setImage("tb-image;" + imageInfo.getLink());
deviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
DeviceProfile deviceProfileInlined = doGet("/api/deviceProfile/" + deviceProfile.getUuidId() + "?inlineImages=true", DeviceProfile.class);
assertThat(deviceProfileInlined.getImage()).isEqualTo("tb-image:" +
Base64.getEncoder().encodeToString(imageInfo.getResourceKey().getBytes()) + ":"
+ Base64.getEncoder().encodeToString(imageInfo.getName().getBytes()) + ":"
+ Base64.getEncoder().encodeToString(imageInfo.getResourceSubType().name().getBytes()) +
";data:image/png;base64," + Base64.getEncoder().encodeToString(PNG_IMAGE));
deviceProfile = doGet("/api/deviceProfile/" + deviceProfile.getUuidId(), DeviceProfile.class);
assertThat(deviceProfile.getImage()).isEqualTo("tb-image;" + imageInfo.getLink());
}
private TbResourceInfo updateImagePublicStatus(String filename, boolean isPublic) throws Exception { private TbResourceInfo updateImagePublicStatus(String filename, boolean isPublic) throws Exception {
return doPut("/api/images/tenant/" + filename + "/public/" + isPublic, "", TbResourceInfo.class); return doPut("/api/images/tenant/" + filename + "/public/" + isPublic, "", TbResourceInfo.class);
} }

37
application/src/test/java/org/thingsboard/server/controller/RuleEngineControllerTest.java

@ -62,7 +62,12 @@ public class RuleEngineControllerTest extends AbstractControllerTest {
@Test @Test
public void testHandleRuleEngineRequestWithMsgOriginatorUser() throws Exception { public void testHandleRuleEngineRequestWithMsgOriginatorUser() throws Exception {
loginSysAdmin(); loginSysAdmin();
TbMsg responseMsg = TbMsg.newMsg(TbMsgType.REST_API_REQUEST, currentUserId, TbMsgMetaData.EMPTY, RESPONSE_BODY); TbMsg responseMsg = TbMsg.newMsg()
.type(TbMsgType.REST_API_REQUEST)
.originator(currentUserId)
.copyMetaData(TbMsgMetaData.EMPTY)
.data(RESPONSE_BODY)
.build();
mockRestApiCallToRuleEngine(responseMsg); mockRestApiCallToRuleEngine(responseMsg);
JsonNode apiResponse = doPostAsyncWithTypedResponse("/api/rule-engine/", REQUEST_BODY, new TypeReference<>() { JsonNode apiResponse = doPostAsyncWithTypedResponse("/api/rule-engine/", REQUEST_BODY, new TypeReference<>() {
@ -86,7 +91,12 @@ public class RuleEngineControllerTest extends AbstractControllerTest {
loginTenantAdmin(); loginTenantAdmin();
Device device = createDevice("Test", "123"); Device device = createDevice("Test", "123");
DeviceId deviceId = device.getId(); DeviceId deviceId = device.getId();
TbMsg responseMsg = TbMsg.newMsg(TbMsgType.REST_API_REQUEST, deviceId, TbMsgMetaData.EMPTY, RESPONSE_BODY); TbMsg responseMsg = TbMsg.newMsg()
.type(TbMsgType.REST_API_REQUEST)
.originator(deviceId)
.copyMetaData(TbMsgMetaData.EMPTY)
.data(RESPONSE_BODY)
.build();
mockRestApiCallToRuleEngine(responseMsg); mockRestApiCallToRuleEngine(responseMsg);
JsonNode apiResponse = doPostAsyncWithTypedResponse("/api/rule-engine/DEVICE/" + deviceId.getId(), REQUEST_BODY, new TypeReference<>() { JsonNode apiResponse = doPostAsyncWithTypedResponse("/api/rule-engine/DEVICE/" + deviceId.getId(), REQUEST_BODY, new TypeReference<>() {
@ -110,7 +120,12 @@ public class RuleEngineControllerTest extends AbstractControllerTest {
loginTenantAdmin(); loginTenantAdmin();
Device device = createDevice("Test", "123"); Device device = createDevice("Test", "123");
DeviceId deviceId = device.getId(); DeviceId deviceId = device.getId();
TbMsg responseMsg = TbMsg.newMsg(TbMsgType.REST_API_REQUEST, deviceId, TbMsgMetaData.EMPTY, RESPONSE_BODY); TbMsg responseMsg = TbMsg.newMsg()
.type(TbMsgType.REST_API_REQUEST)
.originator(deviceId)
.copyMetaData(TbMsgMetaData.EMPTY)
.data(RESPONSE_BODY)
.build();
mockRestApiCallToRuleEngine(responseMsg); mockRestApiCallToRuleEngine(responseMsg);
JsonNode apiResponse = doPostAsyncWithTypedResponse("/api/rule-engine/DEVICE/" + deviceId.getId() + "/15000", REQUEST_BODY, new TypeReference<>() { JsonNode apiResponse = doPostAsyncWithTypedResponse("/api/rule-engine/DEVICE/" + deviceId.getId() + "/15000", REQUEST_BODY, new TypeReference<>() {
@ -156,7 +171,13 @@ public class RuleEngineControllerTest extends AbstractControllerTest {
loginTenantAdmin(); loginTenantAdmin();
Device device = createDevice("Test", "123"); Device device = createDevice("Test", "123");
DeviceId deviceId = device.getId(); DeviceId deviceId = device.getId();
TbMsg responseMsg = TbMsg.newMsg(DataConstants.HP_QUEUE_NAME, TbMsgType.REST_API_REQUEST, deviceId, TbMsgMetaData.EMPTY, RESPONSE_BODY); TbMsg responseMsg = TbMsg.newMsg()
.queueName(DataConstants.HP_QUEUE_NAME)
.type(TbMsgType.REST_API_REQUEST)
.originator(deviceId)
.copyMetaData(TbMsgMetaData.EMPTY)
.data(RESPONSE_BODY)
.build();
mockRestApiCallToRuleEngine(responseMsg); mockRestApiCallToRuleEngine(responseMsg);
JsonNode apiResponse = doPostAsyncWithTypedResponse("/api/rule-engine/DEVICE/" + deviceId.getId() + "/HighPriority/1000", REQUEST_BODY, new TypeReference<>() { JsonNode apiResponse = doPostAsyncWithTypedResponse("/api/rule-engine/DEVICE/" + deviceId.getId() + "/HighPriority/1000", REQUEST_BODY, new TypeReference<>() {
@ -195,7 +216,13 @@ public class RuleEngineControllerTest extends AbstractControllerTest {
assignDeviceToCustomer(deviceId, customerId); assignDeviceToCustomer(deviceId, customerId);
loginCustomerUser(); loginCustomerUser();
TbMsg responseMsg = TbMsg.newMsg(TbMsgType.REST_API_REQUEST, deviceId, customerId, TbMsgMetaData.EMPTY, RESPONSE_BODY); TbMsg responseMsg = TbMsg.newMsg()
.type(TbMsgType.REST_API_REQUEST)
.originator(deviceId)
.customerId(customerId)
.copyMetaData(TbMsgMetaData.EMPTY)
.data(RESPONSE_BODY)
.build();
mockRestApiCallToRuleEngine(responseMsg); mockRestApiCallToRuleEngine(responseMsg);
JsonNode apiResponse = doPostAsyncWithTypedResponse("/api/rule-engine/DEVICE/" + deviceId.getId(), REQUEST_BODY, new TypeReference<>() { JsonNode apiResponse = doPostAsyncWithTypedResponse("/api/rule-engine/DEVICE/" + deviceId.getId(), REQUEST_BODY, new TypeReference<>() {

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

@ -743,7 +743,12 @@ public class TenantControllerTest extends AbstractControllerTest {
} }
private TbMsg publishTbMsg(TenantId tenantId, TopicPartitionInfo tpi) { private TbMsg publishTbMsg(TenantId tenantId, TopicPartitionInfo tpi) {
TbMsg tbMsg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, tenantId, TbMsgMetaData.EMPTY, "{\"test\":1}"); TbMsg tbMsg = TbMsg.newMsg()
.type(TbMsgType.POST_TELEMETRY_REQUEST)
.originator(tenantId)
.copyMetaData(TbMsgMetaData.EMPTY)
.data("{\"test\":1}")
.build();
TransportProtos.ToRuleEngineMsg msg = TransportProtos.ToRuleEngineMsg.newBuilder() TransportProtos.ToRuleEngineMsg msg = TransportProtos.ToRuleEngineMsg.newBuilder()
.setTenantIdMSB(tenantId.getId().getMostSignificantBits()) .setTenantIdMSB(tenantId.getId().getMostSignificantBits())
.setTenantIdLSB(tenantId.getId().getLeastSignificantBits()) .setTenantIdLSB(tenantId.getId().getLeastSignificantBits())

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

@ -29,6 +29,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.TestPropertySource;
import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils; import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.AttributesSaveRequest;
import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmSeverity;
@ -804,19 +806,24 @@ public class WebsocketApiTest extends AbstractControllerTest {
private void sendTelemetry(Device device, List<TsKvEntry> tsData) throws InterruptedException { private void sendTelemetry(Device device, List<TsKvEntry> tsData) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1); CountDownLatch latch = new CountDownLatch(1);
tsService.saveAndNotify(device.getTenantId(), null, device.getId(), tsData, 0, new FutureCallback<Void>() { tsService.saveTimeseries(TimeseriesSaveRequest.builder()
@Override .tenantId(device.getTenantId())
public void onSuccess(@Nullable Void result) { .entityId(device.getId())
log.debug("sendTelemetry callback onSuccess"); .entries(tsData)
latch.countDown(); .callback(new FutureCallback<Void>() {
} @Override
public void onSuccess(@Nullable Void result) {
@Override log.debug("sendTelemetry callback onSuccess");
public void onFailure(Throwable t) { latch.countDown();
log.error("Failed to send telemetry", t); }
latch.countDown();
} @Override
}); public void onFailure(Throwable t) {
log.error("Failed to send telemetry", t);
latch.countDown();
}
})
.build());
assertThat(latch.await(TIMEOUT, TimeUnit.SECONDS)).as("await sendTelemetry callback"); assertThat(latch.await(TIMEOUT, TimeUnit.SECONDS)).as("await sendTelemetry callback");
} }
@ -826,19 +833,26 @@ public class WebsocketApiTest extends AbstractControllerTest {
private void sendAttributes(TenantId tenantId, EntityId entityId, TbAttributeSubscriptionScope scope, List<AttributeKvEntry> attrData) throws InterruptedException { private void sendAttributes(TenantId tenantId, EntityId entityId, TbAttributeSubscriptionScope scope, List<AttributeKvEntry> attrData) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1); CountDownLatch latch = new CountDownLatch(1);
tsService.saveAndNotify(tenantId, entityId, scope.getAttributeScope(), attrData, new FutureCallback<Void>() { tsService.saveAttributes(AttributesSaveRequest.builder()
@Override .tenantId(tenantId)
public void onSuccess(@Nullable Void result) { .entityId(entityId)
log.debug("sendAttributes callback onSuccess"); .scope(scope.getAttributeScope())
latch.countDown(); .entries(attrData)
} .callback(new FutureCallback<>() {
@Override
@Override public void onSuccess(@Nullable Void result) {
public void onFailure(Throwable t) { log.debug("sendAttributes callback onSuccess");
log.error("Failed to sendAttributes", t); latch.countDown();
latch.countDown(); }
}
}); @Override
public void onFailure(Throwable t) {
log.error("Failed to sendAttributes", t);
latch.countDown();
}
})
.build());
assertThat(latch.await(TIMEOUT, TimeUnit.SECONDS)).as("await sendAttributes callback").isTrue(); assertThat(latch.await(TIMEOUT, TimeUnit.SECONDS)).as("await sendAttributes callback").isTrue();
} }
} }

16
application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java

@ -184,7 +184,13 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
TbMsgCallback tbMsgCallback = Mockito.mock(TbMsgCallback.class); TbMsgCallback tbMsgCallback = Mockito.mock(TbMsgCallback.class);
Mockito.when(tbMsgCallback.isMsgValid()).thenReturn(true); Mockito.when(tbMsgCallback.isMsgValid()).thenReturn(true);
TbMsg tbMsg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, device.getId(), TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT, tbMsgCallback); TbMsg tbMsg = TbMsg.newMsg()
.type(TbMsgType.POST_TELEMETRY_REQUEST)
.originator(device.getId())
.copyMetaData(TbMsgMetaData.EMPTY)
.data(TbMsg.EMPTY_JSON_OBJECT)
.callback(tbMsgCallback)
.build();
QueueToRuleEngineMsg qMsg = new QueueToRuleEngineMsg(savedTenant.getId(), tbMsg, null, null); QueueToRuleEngineMsg qMsg = new QueueToRuleEngineMsg(savedTenant.getId(), tbMsg, null, null);
// Pushing Message to the system // Pushing Message to the system
actorSystem.tell(qMsg); actorSystem.tell(qMsg);
@ -309,7 +315,13 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
TbMsgCallback tbMsgCallback = Mockito.mock(TbMsgCallback.class); TbMsgCallback tbMsgCallback = Mockito.mock(TbMsgCallback.class);
Mockito.when(tbMsgCallback.isMsgValid()).thenReturn(true); Mockito.when(tbMsgCallback.isMsgValid()).thenReturn(true);
TbMsg tbMsg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, device.getId(), TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT, tbMsgCallback); TbMsg tbMsg = TbMsg.newMsg()
.type(TbMsgType.POST_TELEMETRY_REQUEST)
.originator(device.getId())
.copyMetaData(TbMsgMetaData.EMPTY)
.data(TbMsg.EMPTY_JSON_OBJECT)
.callback(tbMsgCallback)
.build();
QueueToRuleEngineMsg qMsg = new QueueToRuleEngineMsg(savedTenant.getId(), tbMsg, null, null); QueueToRuleEngineMsg qMsg = new QueueToRuleEngineMsg(savedTenant.getId(), tbMsg, null, null);
// Pushing Message to the system // Pushing Message to the system
actorSystem.tell(qMsg); actorSystem.tell(qMsg);

8
application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java

@ -142,7 +142,13 @@ public abstract class AbstractRuleEngineLifecycleIntegrationTest extends Abstrac
log.warn("attr updated"); log.warn("attr updated");
TbMsgCallback tbMsgCallback = Mockito.mock(TbMsgCallback.class); TbMsgCallback tbMsgCallback = Mockito.mock(TbMsgCallback.class);
Mockito.when(tbMsgCallback.isMsgValid()).thenReturn(true); Mockito.when(tbMsgCallback.isMsgValid()).thenReturn(true);
TbMsg tbMsg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, device.getId(), TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT, tbMsgCallback); TbMsg tbMsg = TbMsg.newMsg()
.type(TbMsgType.POST_TELEMETRY_REQUEST)
.originator(device.getId())
.copyMetaData(TbMsgMetaData.EMPTY)
.data(TbMsg.EMPTY_JSON_OBJECT)
.callback(tbMsgCallback)
.build();
QueueToRuleEngineMsg qMsg = new QueueToRuleEngineMsg(tenantId, tbMsg, null, null); QueueToRuleEngineMsg qMsg = new QueueToRuleEngineMsg(tenantId, tbMsg, null, null);
// Pushing Message to the system // Pushing Message to the system
log.warn("before tell tbMsgCallback"); log.warn("before tell tbMsgCallback");

8
application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java

@ -274,7 +274,6 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
Device device = createDevice("Device with alarm", "233"); Device device = createDevice("Device with alarm", "233");
NotificationTarget target = createNotificationTarget(tenantAdminUserId); NotificationTarget target = createNotificationTarget(tenantAdminUserId);
defaultNotifications.create(tenantId, DefaultNotifications.newAlarm, target.getId()); defaultNotifications.create(tenantId, DefaultNotifications.newAlarm, target.getId());
defaultNotifications.create(tenantId, DefaultNotifications.entityAction, target.getId());
notificationRulesCache.evict(tenantId); notificationRulesCache.evict(tenantId);
Alarm alarm = new Alarm(); Alarm alarm = new Alarm();
@ -287,7 +286,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
.pollDelay(2, TimeUnit.SECONDS) .pollDelay(2, TimeUnit.SECONDS)
.untilAsserted(() -> { .untilAsserted(() -> {
List<Notification> notifications = getMyNotifications(false, 10); List<Notification> notifications = getMyNotifications(false, 10);
assertThat(notifications).hasSize(1).first().matches(notification -> { assertThat(notifications).singleElement().matches(notification -> {
return notification.getType() == NotificationType.ALARM && return notification.getType() == NotificationType.ALARM &&
notification.getSubject().equals("New alarm 'testAlarm'"); notification.getSubject().equals("New alarm 'testAlarm'");
}); });
@ -353,12 +352,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
.findFirst().orElse(null); .findFirst().orElse(null);
assertThat(scheduledNotificationRequest).extracting(NotificationRequest::getInfo).isEqualTo(notification.getInfo()); assertThat(scheduledNotificationRequest).extracting(NotificationRequest::getInfo).isEqualTo(notification.getInfo());
getWsClient().registerWaitForUpdate();
alarmSubscriptionService.clearAlarm(tenantId, alarm.getId(), System.currentTimeMillis(), null); alarmSubscriptionService.clearAlarm(tenantId, alarm.getId(), System.currentTimeMillis(), null);
getWsClient().waitForUpdate(true);
notification = getWsClient().getLastDataUpdate().getUpdate();
assertThat(notification.getSubject()).isEqualTo("critical alarm '" + alarmType + "' is CLEARED_UNACK");
await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> { await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> {
assertThat(findNotificationRequests(EntityType.ALARM).getData()).filteredOn(NotificationRequest::isScheduled).isEmpty(); assertThat(findNotificationRequests(EntityType.ALARM).getData()).filteredOn(NotificationRequest::isScheduled).isEmpty();
}); });

40
application/src/test/java/org/thingsboard/server/service/queue/DefaultTbClusterServiceTest.java

@ -291,7 +291,13 @@ public class DefaultTbClusterServiceTest {
TbQueueCallback callback = mock(TbQueueCallback.class); TbQueueCallback callback = mock(TbQueueCallback.class);
TenantId tenantId = TenantId.fromUUID(UUID.fromString("3c8bd350-1239-4a3b-b9c3-4dd76f8e20f1")); TenantId tenantId = TenantId.fromUUID(UUID.fromString("3c8bd350-1239-4a3b-b9c3-4dd76f8e20f1"));
TbMsg requestMsg = TbMsg.newMsg(DataConstants.HP_QUEUE_NAME, TbMsgType.REST_API_REQUEST, tenantId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); TbMsg requestMsg = TbMsg.newMsg()
.queueName(DataConstants.HP_QUEUE_NAME)
.type(TbMsgType.REST_API_REQUEST)
.originator(tenantId)
.copyMetaData(TbMsgMetaData.EMPTY)
.data(TbMsg.EMPTY_JSON_OBJECT)
.build();
when(producerProvider.getRuleEngineMsgProducer()).thenReturn(tbREQueueProducer); when(producerProvider.getRuleEngineMsgProducer()).thenReturn(tbREQueueProducer);
@ -305,7 +311,12 @@ public class DefaultTbClusterServiceTest {
public void testPushMsgToRuleEngineWithTenantIdIsNullUuidAndEntityIsDevice() { public void testPushMsgToRuleEngineWithTenantIdIsNullUuidAndEntityIsDevice() {
TenantId tenantId = TenantId.SYS_TENANT_ID; TenantId tenantId = TenantId.SYS_TENANT_ID;
DeviceId deviceId = new DeviceId(UUID.fromString("aa6d112d-2914-4a22-a9e3-bee33edbdb14")); DeviceId deviceId = new DeviceId(UUID.fromString("aa6d112d-2914-4a22-a9e3-bee33edbdb14"));
TbMsg requestMsg = TbMsg.newMsg(TbMsgType.REST_API_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); TbMsg requestMsg = TbMsg.newMsg()
.type(TbMsgType.REST_API_REQUEST)
.originator(deviceId)
.copyMetaData(TbMsgMetaData.EMPTY)
.data(TbMsg.EMPTY_JSON_OBJECT)
.build();
TbQueueCallback callback = mock(TbQueueCallback.class); TbQueueCallback callback = mock(TbQueueCallback.class);
clusterService.pushMsgToRuleEngine(tenantId, deviceId, requestMsg, false, callback); clusterService.pushMsgToRuleEngine(tenantId, deviceId, requestMsg, false, callback);
@ -321,7 +332,13 @@ public class DefaultTbClusterServiceTest {
TenantId tenantId = TenantId.fromUUID(UUID.fromString("3c8bd350-1239-4a3b-b9c3-4dd76f8e20f1")); TenantId tenantId = TenantId.fromUUID(UUID.fromString("3c8bd350-1239-4a3b-b9c3-4dd76f8e20f1"));
DeviceId deviceId = new DeviceId(UUID.fromString("adbb9d41-3367-40fd-9e74-7dd7cc5d30cf")); DeviceId deviceId = new DeviceId(UUID.fromString("adbb9d41-3367-40fd-9e74-7dd7cc5d30cf"));
DeviceProfile deviceProfile = new DeviceProfile(new DeviceProfileId(UUID.fromString("552f5d6d-0b2b-43e1-a7d2-a51cb2a96927"))); DeviceProfile deviceProfile = new DeviceProfile(new DeviceProfileId(UUID.fromString("552f5d6d-0b2b-43e1-a7d2-a51cb2a96927")));
TbMsg requestMsg = TbMsg.newMsg(DataConstants.HP_QUEUE_NAME, TbMsgType.REST_API_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); TbMsg requestMsg = TbMsg.newMsg()
.queueName(DataConstants.HP_QUEUE_NAME)
.type(TbMsgType.REST_API_REQUEST)
.originator(deviceId)
.copyMetaData(TbMsgMetaData.EMPTY)
.data(TbMsg.EMPTY_JSON_OBJECT)
.build();
when(deviceProfileCache.get(any(TenantId.class), any(DeviceId.class))).thenReturn(deviceProfile); when(deviceProfileCache.get(any(TenantId.class), any(DeviceId.class))).thenReturn(deviceProfile);
when(producerProvider.getRuleEngineMsgProducer()).thenReturn(tbREQueueProducer); when(producerProvider.getRuleEngineMsgProducer()).thenReturn(tbREQueueProducer);
@ -341,7 +358,12 @@ public class DefaultTbClusterServiceTest {
DeviceId deviceId = new DeviceId(UUID.fromString("016c2abb-f46f-49f9-a83d-4d28b803cfe6")); DeviceId deviceId = new DeviceId(UUID.fromString("016c2abb-f46f-49f9-a83d-4d28b803cfe6"));
DeviceProfile deviceProfile = new DeviceProfile(new DeviceProfileId(UUID.fromString("dc5766e2-1a32-4022-859b-743050097ab7"))); DeviceProfile deviceProfile = new DeviceProfile(new DeviceProfileId(UUID.fromString("dc5766e2-1a32-4022-859b-743050097ab7")));
deviceProfile.setDefaultQueueName(DataConstants.MAIN_QUEUE_NAME); deviceProfile.setDefaultQueueName(DataConstants.MAIN_QUEUE_NAME);
TbMsg requestMsg = TbMsg.newMsg(TbMsgType.REST_API_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); TbMsg requestMsg = TbMsg.newMsg()
.type(TbMsgType.REST_API_REQUEST)
.originator(deviceId)
.copyMetaData(TbMsgMetaData.EMPTY)
.data(TbMsg.EMPTY_JSON_OBJECT)
.build();
when(deviceProfileCache.get(any(TenantId.class), any(DeviceId.class))).thenReturn(deviceProfile); when(deviceProfileCache.get(any(TenantId.class), any(DeviceId.class))).thenReturn(deviceProfile);
when(producerProvider.getRuleEngineMsgProducer()).thenReturn(tbREQueueProducer); when(producerProvider.getRuleEngineMsgProducer()).thenReturn(tbREQueueProducer);
@ -349,7 +371,7 @@ public class DefaultTbClusterServiceTest {
clusterService.pushMsgToRuleEngine(tenantId, deviceId, requestMsg, false, callback); clusterService.pushMsgToRuleEngine(tenantId, deviceId, requestMsg, false, callback);
verify(producerProvider).getRuleEngineMsgProducer(); verify(producerProvider).getRuleEngineMsgProducer();
TbMsg expectedMsg = TbMsg.transformMsgQueueName(requestMsg, DataConstants.MAIN_QUEUE_NAME); TbMsg expectedMsg = requestMsg.transform(DataConstants.MAIN_QUEUE_NAME);
ArgumentCaptor<TbMsg> actualMsg = ArgumentCaptor.forClass(TbMsg.class); ArgumentCaptor<TbMsg> actualMsg = ArgumentCaptor.forClass(TbMsg.class);
verify(ruleEngineProducerService).sendToRuleEngine(eq(tbREQueueProducer), eq(tenantId), actualMsg.capture(), eq(callback)); verify(ruleEngineProducerService).sendToRuleEngine(eq(tbREQueueProducer), eq(tenantId), actualMsg.capture(), eq(callback));
assertThat(actualMsg.getValue()).usingRecursiveComparison().ignoringFields("ctx").isEqualTo(expectedMsg); assertThat(actualMsg.getValue()).usingRecursiveComparison().ignoringFields("ctx").isEqualTo(expectedMsg);
@ -375,12 +397,12 @@ public class DefaultTbClusterServiceTest {
device.setDeviceProfileId(deviceProfileId); device.setDeviceProfileId(deviceProfileId);
// device updated // device updated
TbMsg tbMsg = TbMsg.builder().internalType(TbMsgType.ENTITY_UPDATED).build(); TbMsg tbMsg = TbMsg.newMsg().type(TbMsgType.ENTITY_UPDATED).build();
((DefaultTbClusterService) clusterService).getRuleEngineProfileForEntityOrElseNull(tenantId, deviceId, tbMsg); ((DefaultTbClusterService) clusterService).getRuleEngineProfileForEntityOrElseNull(tenantId, deviceId, tbMsg);
verify(deviceProfileCache, times(1)).get(tenantId, deviceId); verify(deviceProfileCache, times(1)).get(tenantId, deviceId);
// device deleted // device deleted
tbMsg = TbMsg.builder().internalType(TbMsgType.ENTITY_DELETED).data(JacksonUtil.toString(device)).build(); tbMsg = TbMsg.newMsg().type(TbMsgType.ENTITY_DELETED).data(JacksonUtil.toString(device)).build();
((DefaultTbClusterService) clusterService).getRuleEngineProfileForEntityOrElseNull(tenantId, deviceId, tbMsg); ((DefaultTbClusterService) clusterService).getRuleEngineProfileForEntityOrElseNull(tenantId, deviceId, tbMsg);
verify(deviceProfileCache, times(1)).get(tenantId, deviceProfileId); verify(deviceProfileCache, times(1)).get(tenantId, deviceProfileId);
} }
@ -395,12 +417,12 @@ public class DefaultTbClusterServiceTest {
asset.setAssetProfileId(assetProfileId); asset.setAssetProfileId(assetProfileId);
// asset updated // asset updated
TbMsg tbMsg = TbMsg.builder().internalType(TbMsgType.ENTITY_UPDATED).build(); TbMsg tbMsg = TbMsg.newMsg().type(TbMsgType.ENTITY_UPDATED).build();
((DefaultTbClusterService) clusterService).getRuleEngineProfileForEntityOrElseNull(tenantId, assetId, tbMsg); ((DefaultTbClusterService) clusterService).getRuleEngineProfileForEntityOrElseNull(tenantId, assetId, tbMsg);
verify(assetProfileCache, times(1)).get(tenantId, assetId); verify(assetProfileCache, times(1)).get(tenantId, assetId);
// asset deleted // asset deleted
tbMsg = TbMsg.builder().internalType(TbMsgType.ENTITY_DELETED).data(JacksonUtil.toString(asset)).build(); tbMsg = TbMsg.newMsg().type(TbMsgType.ENTITY_DELETED).data(JacksonUtil.toString(asset)).build();
((DefaultTbClusterService) clusterService).getRuleEngineProfileForEntityOrElseNull(tenantId, assetId, tbMsg); ((DefaultTbClusterService) clusterService).getRuleEngineProfileForEntityOrElseNull(tenantId, assetId, tbMsg);
verify(assetProfileCache, times(1)).get(tenantId, assetProfileId); verify(assetProfileCache, times(1)).get(tenantId, assetProfileId);
} }

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

@ -782,7 +782,12 @@ public class TbRuleEngineQueueConsumerManagerTest {
} }
public void setUpTestMsg() { public void setUpTestMsg() {
testMsg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, new DeviceId(UUID.randomUUID()), new TbMsgMetaData(), "{}"); testMsg = TbMsg.newMsg()
.type(TbMsgType.POST_TELEMETRY_REQUEST)
.originator(new DeviceId(UUID.randomUUID()))
.copyMetaData(new TbMsgMetaData())
.data("{}")
.build();
} }
} }

2
application/src/test/java/org/thingsboard/server/service/queue/ruleengine/TbRuleEngineStrategyTest.java

@ -248,7 +248,7 @@ public class TbRuleEngineStrategyTest {
} }
private static TbMsg createRandomMsg() { private static TbMsg createRandomMsg() {
return TbMsg.builder() return TbMsg.newMsg()
.id(UUID.randomUUID()) .id(UUID.randomUUID())
.type("test type") .type("test type")
.originator(deviceId) .originator(deviceId)

7
application/src/test/java/org/thingsboard/server/service/rpc/DefaultTbRuleEngineRpcServiceTest.java

@ -46,7 +46,12 @@ class DefaultTbRuleEngineRpcServiceTest {
String serviceId = "tb-core-0"; String serviceId = "tb-core-0";
UUID requestId = UUID.fromString("f64a20df-eb1e-46a3-ba6f-0b3ae053ee0a"); UUID requestId = UUID.fromString("f64a20df-eb1e-46a3-ba6f-0b3ae053ee0a");
DeviceId deviceId = new DeviceId(UUID.fromString("1d9f771a-7cdc-4ac7-838c-ba193d05a012")); DeviceId deviceId = new DeviceId(UUID.fromString("1d9f771a-7cdc-4ac7-838c-ba193d05a012"));
TbMsg msg = TbMsg.newMsg(TbMsgType.REST_API_REQUEST, deviceId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); TbMsg msg = TbMsg.newMsg()
.type(TbMsgType.REST_API_REQUEST)
.originator(deviceId)
.copyMetaData(TbMsgMetaData.EMPTY)
.data(TbMsg.EMPTY_JSON_OBJECT)
.build();
var restApiCallResponseMsgProto = TransportProtos.RestApiCallResponseMsgProto.newBuilder() var restApiCallResponseMsgProto = TransportProtos.RestApiCallResponseMsgProto.newBuilder()
.setRequestIdMSB(requestId.getMostSignificantBits()) .setRequestIdMSB(requestId.getMostSignificantBits())
.setRequestIdLSB(requestId.getLeastSignificantBits()) .setRequestIdLSB(requestId.getLeastSignificantBits())

16
application/src/test/java/org/thingsboard/server/service/ruleengine/DefaultRuleEngineCallServiceTest.java

@ -85,7 +85,13 @@ public class DefaultRuleEngineCallServiceTest {
metaData.put("serviceId", "core"); metaData.put("serviceId", "core");
metaData.put("requestUUID", requestId.toString()); metaData.put("requestUUID", requestId.toString());
metaData.put("expirationTime", Long.toString(expTime)); metaData.put("expirationTime", Long.toString(expTime));
TbMsg msg = TbMsg.newMsg(DataConstants.MAIN_QUEUE_NAME, TbMsgType.REST_API_REQUEST, TENANT_ID, new TbMsgMetaData(metaData), "{\"key\":\"value\"}"); TbMsg msg = TbMsg.newMsg()
.queueName(DataConstants.MAIN_QUEUE_NAME)
.type(TbMsgType.REST_API_REQUEST)
.originator(TENANT_ID)
.copyMetaData(new TbMsgMetaData(metaData))
.data("{\"key\":\"value\"}")
.build();
Consumer<TbMsg> anyConsumer = TbMsg::getData; Consumer<TbMsg> anyConsumer = TbMsg::getData;
doAnswer(invocation -> { doAnswer(invocation -> {
@ -113,7 +119,13 @@ public class DefaultRuleEngineCallServiceTest {
metaData.put("serviceId", "core"); metaData.put("serviceId", "core");
metaData.put("requestUUID", requestId.toString()); metaData.put("requestUUID", requestId.toString());
metaData.put("expirationTime", Long.toString(expTime)); metaData.put("expirationTime", Long.toString(expTime));
TbMsg msg = TbMsg.newMsg(DataConstants.MAIN_QUEUE_NAME, TbMsgType.REST_API_REQUEST, TENANT_ID, new TbMsgMetaData(metaData), "{\"key\":\"value\"}"); TbMsg msg = TbMsg.newMsg()
.queueName(DataConstants.MAIN_QUEUE_NAME)
.type(TbMsgType.REST_API_REQUEST)
.originator(TENANT_ID)
.copyMetaData(new TbMsgMetaData(metaData))
.data("{\"key\":\"value\"}")
.build();
Consumer<TbMsg> anyConsumer = TbMsg::getData; Consumer<TbMsg> anyConsumer = TbMsg::getData;
doAnswer(invocation -> { doAnswer(invocation -> {

12
application/src/test/java/org/thingsboard/server/service/sql/SequentialTimeseriesPersistenceTest.java

@ -131,11 +131,13 @@ public class SequentialTimeseriesPersistenceTest extends AbstractControllerTest
void saveLatestTsForAssetAndDevice(List<Device> devices, Asset asset, int idx) throws ExecutionException, InterruptedException, TimeoutException { void saveLatestTsForAssetAndDevice(List<Device> devices, Asset asset, int idx) throws ExecutionException, InterruptedException, TimeoutException {
for (Device device : devices) { for (Device device : devices) {
TbMsg tbMsg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, TbMsg tbMsg = TbMsg.newMsg()
device.getId(), .type(TbMsgType.POST_TELEMETRY_REQUEST)
getTbMsgMetadata(device.getName(), ts.get(idx)), .originator(device.getId())
TbMsgDataType.JSON, .copyMetaData(getTbMsgMetadata(device.getName(), ts.get(idx)))
getTbMsgData(msgValue.get(idx))); .dataType(TbMsgDataType.JSON)
.data(getTbMsgData(msgValue.get(idx)))
.build();
saveDeviceTsEntry(device.getId(), tbMsg, msgValue.get(idx)); saveDeviceTsEntry(device.getId(), tbMsg, msgValue.get(idx));
saveAssetTsEntry(asset, device.getName(), msgValue.get(idx), TbMsgTimeseriesNode.computeTs(tbMsg, configuration.isUseServerTs())); saveAssetTsEntry(asset, device.getName(), msgValue.get(idx), TbMsgTimeseriesNode.computeTs(tbMsg, configuration.isUseServerTs()));
idx++; idx++;

241
application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java

@ -27,6 +27,7 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.util.ReflectionTestUtils;
import org.thingsboard.rule.engine.api.AttributesSaveRequest;
import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
@ -72,7 +73,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await; import static org.awaitility.Awaitility.await;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then; import static org.mockito.BDDMockito.then;
@ -82,6 +83,7 @@ import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset; import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static org.thingsboard.server.service.state.DefaultDeviceStateService.ACTIVITY_STATE; import static org.thingsboard.server.service.state.DefaultDeviceStateService.ACTIVITY_STATE;
@ -208,9 +210,12 @@ public class DefaultDeviceStateServiceTest {
service.onDeviceConnect(tenantId, deviceId, lastConnectTime); service.onDeviceConnect(tenantId, deviceId, lastConnectTime);
// THEN // THEN
then(telemetrySubscriptionService).should().saveAttrAndNotify( then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), eq(LAST_CONNECT_TIME), eq(lastConnectTime), any() request.getTenantId().equals(TenantId.SYS_TENANT_ID) && request.getEntityId().equals(deviceId) &&
); request.getScope().equals(AttributeScope.SERVER_SCOPE) &&
request.getEntries().get(0).getKey().equals(LAST_CONNECT_TIME) &&
request.getEntries().get(0).getValue().equals(lastConnectTime)
));
var msgCaptor = ArgumentCaptor.forClass(TbMsg.class); var msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
then(clusterService).should().pushMsgToRuleEngine(eq(tenantId), eq(deviceId), msgCaptor.capture(), any()); then(clusterService).should().pushMsgToRuleEngine(eq(tenantId), eq(deviceId), msgCaptor.capture(), any());
@ -292,10 +297,12 @@ public class DefaultDeviceStateServiceTest {
service.onDeviceDisconnect(tenantId, deviceId, lastDisconnectTime); service.onDeviceDisconnect(tenantId, deviceId, lastDisconnectTime);
// THEN // THEN
then(telemetrySubscriptionService).should().saveAttrAndNotify( then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), request.getTenantId().equals(TenantId.SYS_TENANT_ID) && request.getEntityId().equals(deviceId) &&
eq(LAST_DISCONNECT_TIME), eq(lastDisconnectTime), any() request.getScope().equals(AttributeScope.SERVER_SCOPE) &&
); request.getEntries().get(0).getKey().equals(LAST_DISCONNECT_TIME) &&
request.getEntries().get(0).getValue().equals(lastDisconnectTime)
));
var msgCaptor = ArgumentCaptor.forClass(TbMsg.class); var msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
then(clusterService).should().pushMsgToRuleEngine(eq(tenantId), eq(deviceId), msgCaptor.capture(), any()); then(clusterService).should().pushMsgToRuleEngine(eq(tenantId), eq(deviceId), msgCaptor.capture(), any());
@ -413,14 +420,18 @@ public class DefaultDeviceStateServiceTest {
service.onDeviceInactivity(tenantId, deviceId, lastInactivityTime); service.onDeviceInactivity(tenantId, deviceId, lastInactivityTime);
// THEN // THEN
then(telemetrySubscriptionService).should().saveAttrAndNotify( then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), request.getTenantId().equals(TenantId.SYS_TENANT_ID) && request.getEntityId().equals(deviceId) &&
eq(INACTIVITY_ALARM_TIME), eq(lastInactivityTime), any() request.getScope().equals(AttributeScope.SERVER_SCOPE) &&
); request.getEntries().get(0).getKey().equals(INACTIVITY_ALARM_TIME) &&
then(telemetrySubscriptionService).should().saveAttrAndNotify( request.getEntries().get(0).getValue().equals(lastInactivityTime)
eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), ));
eq(ACTIVITY_STATE), eq(false), any() then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
); request.getTenantId().equals(TenantId.SYS_TENANT_ID) && request.getEntityId().equals(deviceId) &&
request.getScope().equals(AttributeScope.SERVER_SCOPE) &&
request.getEntries().get(0).getKey().equals(ACTIVITY_STATE) &&
request.getEntries().get(0).getValue().equals(false)
));
var msgCaptor = ArgumentCaptor.forClass(TbMsg.class); var msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
then(clusterService).should() then(clusterService).should()
@ -453,14 +464,17 @@ public class DefaultDeviceStateServiceTest {
service.updateInactivityStateIfExpired(System.currentTimeMillis(), deviceId, deviceStateData); service.updateInactivityStateIfExpired(System.currentTimeMillis(), deviceId, deviceStateData);
// THEN // THEN
then(telemetrySubscriptionService).should().saveAttrAndNotify( then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), request.getTenantId().equals(TenantId.SYS_TENANT_ID) && request.getEntityId().equals(deviceId) &&
eq(INACTIVITY_ALARM_TIME), anyLong(), any() request.getScope().equals(AttributeScope.SERVER_SCOPE) &&
); request.getEntries().get(0).getKey().equals(INACTIVITY_ALARM_TIME)
then(telemetrySubscriptionService).should().saveAttrAndNotify( ));
eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
eq(ACTIVITY_STATE), eq(false), any() request.getTenantId().equals(TenantId.SYS_TENANT_ID) && request.getEntityId().equals(deviceId) &&
); request.getScope().equals(AttributeScope.SERVER_SCOPE) &&
request.getEntries().get(0).getKey().equals(ACTIVITY_STATE) &&
request.getEntries().get(0).getValue().equals(false)
));
var msgCaptor = ArgumentCaptor.forClass(TbMsg.class); var msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
then(clusterService).should() then(clusterService).should()
@ -612,7 +626,9 @@ public class DefaultDeviceStateServiceTest {
long newTimeout = System.currentTimeMillis() - deviceState.getLastActivityTime() + increase; long newTimeout = System.currentTimeMillis() - deviceState.getLastActivityTime() + increase;
service.onDeviceInactivityTimeoutUpdate(tenantId, deviceId, newTimeout); service.onDeviceInactivityTimeoutUpdate(tenantId, deviceId, newTimeout);
verify(telemetrySubscriptionService, never()).saveAttrAndNotify(any(), eq(deviceId), any(AttributeScope.class), eq(ACTIVITY_STATE), any(), any()); verify(telemetrySubscriptionService, never()).saveAttributes(argThat(request ->
request.getEntityId().equals(deviceId) && request.getEntries().get(0).getKey().equals(ACTIVITY_STATE)
));
Thread.sleep(defaultTimeout + increase); Thread.sleep(defaultTimeout + increase);
service.checkStates(); service.checkStates();
activityVerify(false); activityVerify(false);
@ -651,7 +667,9 @@ public class DefaultDeviceStateServiceTest {
long newTimeout = 1; long newTimeout = 1;
Thread.sleep(newTimeout); Thread.sleep(newTimeout);
verify(telemetrySubscriptionService, never()).saveAttrAndNotify(any(), eq(deviceId), any(AttributeScope.class), eq(ACTIVITY_STATE), any(), any()); verify(telemetrySubscriptionService, never()).saveAttributes(argThat(request ->
request.getEntityId().equals(deviceId) && request.getEntries().get(0).getKey().equals(ACTIVITY_STATE)
));
} }
@Test @Test
@ -672,8 +690,6 @@ public class DefaultDeviceStateServiceTest {
service.onDeviceActivity(tenantId, deviceId, System.currentTimeMillis()); service.onDeviceActivity(tenantId, deviceId, System.currentTimeMillis());
activityVerify(true); activityVerify(true);
verify(telemetrySubscriptionService, never()).saveAttrAndNotify(any(), eq(deviceId), any(AttributeScope.class), eq(ACTIVITY_STATE), any(), any());
long newTimeout = 1; long newTimeout = 1;
Thread.sleep(newTimeout); Thread.sleep(newTimeout);
@ -713,11 +729,17 @@ public class DefaultDeviceStateServiceTest {
long newTimeout = 1; long newTimeout = 1;
service.onDeviceInactivityTimeoutUpdate(tenantId, deviceId, newTimeout); service.onDeviceInactivityTimeoutUpdate(tenantId, deviceId, newTimeout);
verify(telemetrySubscriptionService, never()).saveAttrAndNotify(any(), eq(deviceId), any(AttributeScope.class), eq(ACTIVITY_STATE), any(), any()); verify(telemetrySubscriptionService, never()).saveAttributes(argThat(request ->
request.getEntityId().equals(deviceId) && request.getEntries().get(0).getKey().equals(ACTIVITY_STATE)
));
} }
private void activityVerify(boolean isActive) { private void activityVerify(boolean isActive) {
verify(telemetrySubscriptionService).saveAttrAndNotify(any(), eq(deviceId), any(AttributeScope.class), eq(ACTIVITY_STATE), eq(isActive), any()); verify(telemetrySubscriptionService).saveAttributes(argThat(request ->
request.getEntityId().equals(deviceId) &&
request.getEntries().get(0).getKey().equals(ACTIVITY_STATE) &&
request.getEntries().get(0).getValue().equals(isActive)
));
} }
@Test @Test
@ -763,21 +785,27 @@ public class DefaultDeviceStateServiceTest {
// THEN // THEN
assertThat(deviceState.isActive()).isEqualTo(true); assertThat(deviceState.isActive()).isEqualTo(true);
assertThat(deviceState.getLastActivityTime()).isEqualTo(lastReportedActivity); assertThat(deviceState.getLastActivityTime()).isEqualTo(lastReportedActivity);
then(telemetrySubscriptionService).should().saveAttrAndNotify( then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
any(), eq(deviceId), any(AttributeScope.class), eq(LAST_ACTIVITY_TIME), eq(lastReportedActivity), any() request.getEntityId().equals(deviceId) &&
); request.getEntries().get(0).getKey().equals(LAST_ACTIVITY_TIME) &&
request.getEntries().get(0).getValue().equals(lastReportedActivity)
));
assertThat(deviceState.getLastInactivityAlarmTime()).isEqualTo(expectedInactivityAlarmTime); assertThat(deviceState.getLastInactivityAlarmTime()).isEqualTo(expectedInactivityAlarmTime);
if (shouldSetInactivityAlarmTimeToZero) { if (shouldSetInactivityAlarmTimeToZero) {
then(telemetrySubscriptionService).should().saveAttrAndNotify( then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
any(), eq(deviceId), any(AttributeScope.class), eq(INACTIVITY_ALARM_TIME), eq(0L), any() request.getEntityId().equals(deviceId) &&
); request.getEntries().get(0).getKey().equals(INACTIVITY_ALARM_TIME) &&
request.getEntries().get(0).getValue().equals(0L)
));
} }
if (shouldUpdateActivityStateToActive) { if (shouldUpdateActivityStateToActive) {
then(telemetrySubscriptionService).should().saveAttrAndNotify( then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), eq(ACTIVITY_STATE), eq(true), any() request.getEntityId().equals(deviceId) &&
); request.getEntries().get(0).getKey().equals(ACTIVITY_STATE) &&
request.getEntries().get(0).getValue().equals(true)
));
var msgCaptor = ArgumentCaptor.forClass(TbMsg.class); var msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
then(clusterService).should().pushMsgToRuleEngine(eq(tenantId), eq(deviceId), msgCaptor.capture(), any()); then(clusterService).should().pushMsgToRuleEngine(eq(tenantId), eq(deviceId), msgCaptor.capture(), any());
@ -796,28 +824,28 @@ public class DefaultDeviceStateServiceTest {
private static Stream<Arguments> provideParametersForUpdateActivityState() { private static Stream<Arguments> provideParametersForUpdateActivityState() {
return Stream.of( return Stream.of(
Arguments.of(true, 100, 120, 80, 80, false, false), Arguments.of(true, 100, 120, 80, 80, false, false),
Arguments.of(true, 100, 120, 100, 100, false, false), Arguments.of(true, 100, 120, 100, 100, false, false),
Arguments.of(false, 100, 120, 110, 110, false, true), Arguments.of(false, 100, 120, 110, 110, false, true),
Arguments.of(true, 100, 100, 80, 80, false, false), Arguments.of(true, 100, 100, 80, 80, false, false),
Arguments.of(true, 100, 100, 100, 100, false, false), Arguments.of(true, 100, 100, 100, 100, false, false),
Arguments.of(false, 100, 100, 110, 0, true, true), Arguments.of(false, 100, 100, 110, 0, true, true),
Arguments.of(false, 100, 110, 110, 0, true, true), Arguments.of(false, 100, 110, 110, 0, true, true),
Arguments.of(false, 100, 110, 120, 0, true, true), Arguments.of(false, 100, 110, 120, 0, true, true),
Arguments.of(true, 0, 0, 0, 0, false, false), Arguments.of(true, 0, 0, 0, 0, false, false),
Arguments.of(false, 0, 0, 0, 0, true, true) Arguments.of(false, 0, 0, 0, 0, true, true)
); );
} }
@ -857,9 +885,10 @@ public class DefaultDeviceStateServiceTest {
assertThat(deviceState.getInactivityTimeout()).isEqualTo(newInactivityTimeout); assertThat(deviceState.getInactivityTimeout()).isEqualTo(newInactivityTimeout);
assertThat(deviceState.isActive()).isEqualTo(expectedActivityState); assertThat(deviceState.isActive()).isEqualTo(expectedActivityState);
if (activityState && !expectedActivityState) { if (activityState && !expectedActivityState) {
then(telemetrySubscriptionService).should().saveAttrAndNotify( then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
any(), eq(deviceId), any(AttributeScope.class), eq(ACTIVITY_STATE), eq(false), any() request.getEntityId().equals(deviceId) && request.getEntries().get(0).getKey().equals(ACTIVITY_STATE) &&
); request.getEntries().get(0).getValue().equals(false)
));
} }
} }
@ -954,9 +983,10 @@ public class DefaultDeviceStateServiceTest {
assertThat(state.getLastInactivityAlarmTime()).isEqualTo(expectedLastInactivityAlarmTime); assertThat(state.getLastInactivityAlarmTime()).isEqualTo(expectedLastInactivityAlarmTime);
if (shouldUpdateActivityStateToInactive) { if (shouldUpdateActivityStateToInactive) {
then(telemetrySubscriptionService).should().saveAttrAndNotify( then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), eq(ACTIVITY_STATE), eq(false), any() request.getEntityId().equals(deviceId) && request.getEntries().get(0).getKey().equals(ACTIVITY_STATE) &&
); request.getEntries().get(0).getValue().equals(false)
));
var msgCaptor = ArgumentCaptor.forClass(TbMsg.class); var msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
then(clusterService).should().pushMsgToRuleEngine(eq(tenantId), eq(deviceId), msgCaptor.capture(), any()); then(clusterService).should().pushMsgToRuleEngine(eq(tenantId), eq(deviceId), msgCaptor.capture(), any());
@ -971,72 +1001,74 @@ public class DefaultDeviceStateServiceTest {
assertThat(actualNotification.getDeviceId()).isEqualTo(deviceId); assertThat(actualNotification.getDeviceId()).isEqualTo(deviceId);
assertThat(actualNotification.isActive()).isFalse(); assertThat(actualNotification.isActive()).isFalse();
then(telemetrySubscriptionService).should().saveAttrAndNotify( then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), request.getTenantId().equals(TenantId.SYS_TENANT_ID) && request.getEntityId().equals(deviceId) &&
eq(INACTIVITY_ALARM_TIME), eq(expectedLastInactivityAlarmTime), any() request.getScope().equals(AttributeScope.SERVER_SCOPE) &&
); request.getEntries().get(0).getKey().equals(INACTIVITY_ALARM_TIME) &&
request.getEntries().get(0).getValue().equals(expectedLastInactivityAlarmTime)
));
} }
} }
private static Stream<Arguments> provideParametersForUpdateInactivityStateIfExpired() { private static Stream<Arguments> provideParametersForUpdateInactivityStateIfExpired() {
return Stream.of( return Stream.of(
Arguments.of(false, 100, 70, 90, 70, 60, false, 90, false), Arguments.of(false, 100, 70, 90, 70, 60, false, 90, false),
Arguments.of(false, 100, 40, 50, 70, 10, false, 50, false), Arguments.of(false, 100, 40, 50, 70, 10, false, 50, false),
Arguments.of(false, 100, 25, 60, 75, 25, false, 60, false), Arguments.of(false, 100, 25, 60, 75, 25, false, 60, false),
Arguments.of(false, 100, 60, 70, 10, 50, false, 70, false), Arguments.of(false, 100, 60, 70, 10, 50, false, 70, false),
Arguments.of(false, 100, 10, 15, 90, 10, false, 15, false), Arguments.of(false, 100, 10, 15, 90, 10, false, 15, false),
Arguments.of(false, 100, 0, 40, 75, 0, false, 40, false), Arguments.of(false, 100, 0, 40, 75, 0, false, 40, false),
Arguments.of(true, 100, 90, 80, 80, 50, true, 80, false), Arguments.of(true, 100, 90, 80, 80, 50, true, 80, false),
Arguments.of(true, 100, 95, 90, 10, 50, true, 90, false), Arguments.of(true, 100, 95, 90, 10, 50, true, 90, false),
Arguments.of(true, 100, 10, 10, 90, 10, false, 100, true), Arguments.of(true, 100, 10, 10, 90, 10, false, 100, true),
Arguments.of(true, 100, 10, 10, 90, 11, true, 10, false), Arguments.of(true, 100, 10, 10, 90, 11, true, 10, false),
Arguments.of(true, 100, 15, 10, 85, 5, false, 100, true), Arguments.of(true, 100, 15, 10, 85, 5, false, 100, true),
Arguments.of(true, 100, 15, 10, 75, 5, false, 100, true), Arguments.of(true, 100, 15, 10, 75, 5, false, 100, true),
Arguments.of(true, 100, 95, 90, 5, 50, false, 100, true), Arguments.of(true, 100, 95, 90, 5, 50, false, 100, true),
Arguments.of(true, 100, 0, 0, 101, 0, true, 0, false), Arguments.of(true, 100, 0, 0, 101, 0, true, 0, false),
Arguments.of(true, 100, 0, 0, 100, 0, false, 100, true), Arguments.of(true, 100, 0, 0, 100, 0, false, 100, true),
Arguments.of(true, 100, 0, 0, 99, 0, false, 100, true), Arguments.of(true, 100, 0, 0, 99, 0, false, 100, true),
Arguments.of(true, 100, 0, 0, 120, 10, true, 0, false), Arguments.of(true, 100, 0, 0, 120, 10, true, 0, false),
Arguments.of(true, 100, 50, 0, 100, 0, true, 0, false), Arguments.of(true, 100, 50, 0, 100, 0, true, 0, false),
Arguments.of(true, 100, 10, 0, 91, 0, true, 0, false), Arguments.of(true, 100, 10, 0, 91, 0, true, 0, false),
Arguments.of(true, 100, 90, 0, 10, 0, false, 100, true), Arguments.of(true, 100, 90, 0, 10, 0, false, 100, true),
Arguments.of(true, 100, 100, 100, 1, 0, true, 100, false), Arguments.of(true, 100, 100, 100, 1, 0, true, 100, false),
Arguments.of(true, 100, 100, 100, 100, 100, true, 100, false), Arguments.of(true, 100, 100, 100, 100, 100, true, 100, false),
Arguments.of(false, 100, 59, 60, 30, 10, false, 60, false), Arguments.of(false, 100, 59, 60, 30, 10, false, 60, false),
Arguments.of(true, 100, 60, 60, 30, 10, false, 100, true), Arguments.of(true, 100, 60, 60, 30, 10, false, 100, true),
Arguments.of(true, 100, 61, 60, 30, 10, false, 100, true), Arguments.of(true, 100, 61, 60, 30, 10, false, 100, true),
Arguments.of(true, 0, 0, 0, 1, 0, true, 0, false), Arguments.of(true, 0, 0, 0, 1, 0, true, 0, false),
Arguments.of(true, 0, 0, 0, 0, 0, false, 0, true), Arguments.of(true, 0, 0, 0, 0, 0, false, 0, true),
Arguments.of(true, 100, 90, 80, 20, 70, true, 80, false), Arguments.of(true, 100, 90, 80, 20, 70, true, 80, false),
Arguments.of(true, 100, 80, 90, 30, 70, true, 90, false) Arguments.of(true, 100, 80, 90, 30, 70, true, 90, false)
); );
} }
@ -1100,7 +1132,10 @@ public class DefaultDeviceStateServiceTest {
// THEN // THEN
await().atMost(1, TimeUnit.SECONDS).untilAsserted(() -> { await().atMost(1, TimeUnit.SECONDS).untilAsserted(() -> {
assertThat(service.deviceStates.get(deviceId).getState().isActive()).isEqualTo(false); assertThat(service.deviceStates.get(deviceId).getState().isActive()).isEqualTo(false);
then(telemetrySubscriptionService).should().saveAttrAndNotify(eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), eq(ACTIVITY_STATE), eq(false), any()); then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
request.getEntityId().equals(deviceId) && request.getEntries().get(0).getKey().equals(ACTIVITY_STATE) &&
request.getEntries().get(0).getValue().equals(false)
));
}); });
} }
@ -1127,10 +1162,31 @@ public class DefaultDeviceStateServiceTest {
service.onDeviceActivity(tenantId, deviceId, currentTime); service.onDeviceActivity(tenantId, deviceId, currentTime);
// THEN // THEN
ArgumentCaptor<AttributesSaveRequest> attributeRequestCaptor = ArgumentCaptor.forClass(AttributesSaveRequest.class);
then(telemetrySubscriptionService).should(times(2)).saveAttributes(attributeRequestCaptor.capture());
await().atMost(1, TimeUnit.SECONDS).untilAsserted(() -> { await().atMost(1, TimeUnit.SECONDS).untilAsserted(() -> {
assertThat(service.deviceStates.get(deviceId).getState().isActive()).isEqualTo(true); assertThat(service.deviceStates.get(deviceId).getState().isActive()).isEqualTo(true);
then(telemetrySubscriptionService).should().saveAttrAndNotify(eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), eq(LAST_ACTIVITY_TIME), eq(currentTime), any());
then(telemetrySubscriptionService).should().saveAttrAndNotify(eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), eq(ACTIVITY_STATE), eq(true), any()); assertThat(attributeRequestCaptor.getAllValues()).hasSize(2)
.anySatisfy(request -> {
assertThat(request.getTenantId()).isEqualTo(TenantId.SYS_TENANT_ID);
assertThat(request.getEntityId()).isEqualTo(deviceId);
assertThat(request.getScope()).isEqualTo(AttributeScope.SERVER_SCOPE);
assertThat(request.getEntries()).singleElement().satisfies(attributeKvEntry -> {
assertThat(attributeKvEntry.getKey()).isEqualTo(LAST_ACTIVITY_TIME);
assertThat(attributeKvEntry.getLongValue()).hasValue(currentTime);
});
})
.anySatisfy(request -> {
assertThat(request.getTenantId()).isEqualTo(TenantId.SYS_TENANT_ID);
assertThat(request.getEntityId()).isEqualTo(deviceId);
assertThat(request.getScope()).isEqualTo(AttributeScope.SERVER_SCOPE);
assertThat(request.getEntries()).singleElement().satisfies(attributeKvEntry -> {
assertThat(attributeKvEntry.getKey()).isEqualTo(ACTIVITY_STATE);
assertThat(attributeKvEntry.getBooleanValue()).hasValue(true);
});
});
}); });
} }
@ -1174,7 +1230,10 @@ public class DefaultDeviceStateServiceTest {
// THEN // THEN
await().atMost(1, TimeUnit.SECONDS).untilAsserted(() -> { await().atMost(1, TimeUnit.SECONDS).untilAsserted(() -> {
assertThat(service.deviceStates.get(deviceId).getState().isActive()).isEqualTo(true); assertThat(service.deviceStates.get(deviceId).getState().isActive()).isEqualTo(true);
then(telemetrySubscriptionService).should().saveAttrAndNotify(eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), eq(ACTIVITY_STATE), eq(true), any()); then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
request.getEntityId().equals(deviceId) && request.getEntries().get(0).getKey().equals(ACTIVITY_STATE) &&
request.getEntries().get(0).getValue().equals(true)
));
}); });
} }

28
common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsSettings.java

@ -21,6 +21,7 @@ import org.eclipse.californium.elements.config.Configuration;
import org.eclipse.californium.elements.util.SslContextUtil; import org.eclipse.californium.elements.util.SslContextUtil;
import org.eclipse.californium.scandium.config.DtlsConnectorConfig; import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
import org.eclipse.californium.scandium.dtls.CertificateType; import org.eclipse.californium.scandium.dtls.CertificateType;
import org.eclipse.californium.scandium.dtls.MaxFragmentLengthExtension.Length;
import org.eclipse.californium.scandium.dtls.x509.SingleCertificateProvider; import org.eclipse.californium.scandium.dtls.x509.SingleCertificateProvider;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
@ -44,6 +45,8 @@ import static org.eclipse.californium.elements.config.CertificateAuthenticationM
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_CLIENT_AUTHENTICATION_MODE; import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_CLIENT_AUTHENTICATION_MODE;
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_CONNECTION_ID_LENGTH; import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_CONNECTION_ID_LENGTH;
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_CONNECTION_ID_NODE_ID; import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_CONNECTION_ID_NODE_ID;
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_MAX_FRAGMENT_LENGTH;
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_MAX_TRANSMISSION_UNIT;
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RETRANSMISSION_TIMEOUT; import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RETRANSMISSION_TIMEOUT;
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_ROLE; import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_ROLE;
import static org.eclipse.californium.scandium.config.DtlsConfig.DtlsRole.SERVER_ONLY; import static org.eclipse.californium.scandium.config.DtlsConfig.DtlsRole.SERVER_ONLY;
@ -66,6 +69,12 @@ public class TbCoapDtlsSettings {
@Value("${coap.dtls.connection_id_length:}") @Value("${coap.dtls.connection_id_length:}")
private Integer cIdLength; private Integer cIdLength;
@Value("${coap.dtls.max_transmission_unit:1024}")
private Integer maxTransmissionUnit;
@Value("${coap.dtls.max_fragment_length:1024}")
private Integer maxFragmentLength;
@Bean @Bean
@ConfigurationProperties(prefix = "coap.dtls.credentials") @ConfigurationProperties(prefix = "coap.dtls.credentials")
public SslCredentialsConfig coapDtlsCredentials() { public SslCredentialsConfig coapDtlsCredentials() {
@ -108,6 +117,15 @@ public class TbCoapDtlsSettings {
configBuilder.set(DTLS_CONNECTION_ID_NODE_ID, null); configBuilder.set(DTLS_CONNECTION_ID_NODE_ID, null);
} }
} }
if (maxTransmissionUnit > 0) {
configBuilder.set(DTLS_MAX_TRANSMISSION_UNIT, maxTransmissionUnit);
}
if (maxFragmentLength > 0) {
Length length = fromLength(maxFragmentLength);
if (length != null) {
configBuilder.set(DTLS_MAX_FRAGMENT_LENGTH, length);
}
}
configBuilder.setAdvancedCertificateVerifier( configBuilder.setAdvancedCertificateVerifier(
new TbCoapDtlsCertificateVerifier( new TbCoapDtlsCertificateVerifier(
transportService, transportService,
@ -127,4 +145,14 @@ public class TbCoapDtlsSettings {
return new InetSocketAddress(addr, port); return new InetSocketAddress(addr, port);
} }
private static Length fromLength(int length) {
for (Length l : Length.values()) {
if (l.length() == length) {
return l;
}
}
return null;
}
} }

6
common/dao-api/src/main/java/org/thingsboard/server/dao/attributes/AttributesService.java

@ -37,16 +37,10 @@ public interface AttributesService {
ListenableFuture<List<AttributeKvEntry>> findAll(TenantId tenantId, EntityId entityId, AttributeScope scope); ListenableFuture<List<AttributeKvEntry>> findAll(TenantId tenantId, EntityId entityId, AttributeScope scope);
@Deprecated(since = "3.7.0")
ListenableFuture<List<Long>> save(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes);
ListenableFuture<List<Long>> save(TenantId tenantId, EntityId entityId, AttributeScope scope, List<AttributeKvEntry> attributes); ListenableFuture<List<Long>> save(TenantId tenantId, EntityId entityId, AttributeScope scope, List<AttributeKvEntry> attributes);
ListenableFuture<Long> save(TenantId tenantId, EntityId entityId, AttributeScope scope, AttributeKvEntry attribute); ListenableFuture<Long> save(TenantId tenantId, EntityId entityId, AttributeScope scope, AttributeKvEntry attribute);
@Deprecated(since = "3.7.0")
ListenableFuture<List<String>> removeAll(TenantId tenantId, EntityId entityId, String scope, List<String> attributeKeys);
ListenableFuture<List<String>> removeAll(TenantId tenantId, EntityId entityId, AttributeScope scope, List<String> attributeKeys); ListenableFuture<List<String>> removeAll(TenantId tenantId, EntityId entityId, AttributeScope scope, List<String> attributeKeys);
List<String> findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId); List<String> findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId);

2
common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestService.java

@ -43,7 +43,7 @@ public interface NotificationRequestService {
List<NotificationRequestId> findNotificationRequestsIdsByStatusAndRuleId(TenantId tenantId, NotificationRequestStatus requestStatus, NotificationRuleId ruleId); List<NotificationRequestId> findNotificationRequestsIdsByStatusAndRuleId(TenantId tenantId, NotificationRequestStatus requestStatus, NotificationRuleId ruleId);
List<NotificationRequest> findNotificationRequestsByRuleIdAndOriginatorEntityId(TenantId tenantId, NotificationRuleId ruleId, EntityId originatorEntityId); List<NotificationRequest> findNotificationRequestsByRuleIdAndOriginatorEntityIdAndStatus(TenantId tenantId, NotificationRuleId ruleId, EntityId originatorEntityId, NotificationRequestStatus status);
void deleteNotificationRequest(TenantId tenantId, NotificationRequest request); void deleteNotificationRequest(TenantId tenantId, NotificationRequest request);

2
common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ImageService.java

@ -64,7 +64,7 @@ public interface ImageService {
boolean updateImagesUsage(WidgetTypeDetails widgetType); boolean updateImagesUsage(WidgetTypeDetails widgetType);
void inlineImage(HasImage entity); <T extends HasImage> T inlineImage(T entity);
Collection<TbResourceInfo> getUsedImages(Dashboard dashboard); Collection<TbResourceInfo> getUsedImages(Dashboard dashboard);

2
common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java

@ -56,7 +56,7 @@ public interface TimeseriesService {
ListenableFuture<List<TsKvLatestRemovingResult>> removeLatest(TenantId tenantId, EntityId entityId, Collection<String> keys); ListenableFuture<List<TsKvLatestRemovingResult>> removeLatest(TenantId tenantId, EntityId entityId, Collection<String> keys);
ListenableFuture<Collection<String>> removeAllLatest(TenantId tenantId, EntityId entityId); ListenableFuture<List<String>> removeAllLatest(TenantId tenantId, EntityId entityId);
List<String> findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId); List<String> findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId);

503
common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java

@ -19,8 +19,6 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import com.google.protobuf.ByteString; import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.InvalidProtocolBufferException;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.Getter; import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -44,8 +42,6 @@ import java.util.UUID;
*/ */
@Data @Data
@Slf4j @Slf4j
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Builder(toBuilder = true)
public final class TbMsg implements Serializable { public final class TbMsg implements Serializable {
public static final String EMPTY_JSON_OBJECT = "{}"; public static final String EMPTY_JSON_OBJECT = "{}";
@ -77,279 +73,55 @@ public final class TbMsg implements Serializable {
@JsonIgnore @JsonIgnore
transient private final TbMsgCallback callback; transient private final TbMsgCallback callback;
public int getAndIncrementRuleNodeCounter() { public static TbMsgBuilder newMsg() {
return ctx.getAndIncrementRuleNodeCounter(); return new TbMsgBuilder();
}
@Deprecated(since = "3.6.0", forRemoval = true)
public static TbMsg newMsg(String queueName, String type, EntityId originator, TbMsgMetaData metaData, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId) {
return newMsg(queueName, type, originator, null, metaData, data, ruleChainId, ruleNodeId);
}
/**
* Creates a new TbMsg instance with the specified parameters.
*
* <p><strong>Deprecated:</strong> This method is deprecated since version 3.6.0 and should only be used when you need to
* specify a custom message type that doesn't exist in the {@link TbMsgType} enum. For standard message types,
* it is recommended to use the {@link #newMsg(String, TbMsgType, EntityId, CustomerId, TbMsgMetaData, String, RuleChainId, RuleNodeId)}
* method instead.</p>
*
* @param queueName the name of the queue where the message will be sent
* @param type the type of the message
* @param originator the originator of the message
* @param customerId the ID of the customer associated with the message
* @param metaData the metadata of the message
* @param data the data of the message
* @param ruleChainId the ID of the rule chain associated with the message
* @param ruleNodeId the ID of the rule node associated with the message
* @return new TbMsg instance
*/
@Deprecated(since = "3.6.0")
public static TbMsg newMsg(String queueName, String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId) {
return new TbMsg(queueName, UUID.randomUUID(), System.currentTimeMillis(), null, type, originator, customerId,
metaData.copy(), TbMsgDataType.JSON, data, ruleChainId, ruleNodeId, null, TbMsgCallback.EMPTY);
}
@Deprecated(since = "3.6.0", forRemoval = true)
public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, String data) {
return newMsg(type, originator, null, metaData, data);
}
@Deprecated(since = "3.6.0", forRemoval = true)
public static TbMsg newMsg(String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data) {
return new TbMsg(null, UUID.randomUUID(), System.currentTimeMillis(), null, type, originator, customerId,
metaData.copy(), TbMsgDataType.JSON, data, null, null, null, TbMsgCallback.EMPTY);
}
public static TbMsg newMsg(String queueName, TbMsgType type, EntityId originator, TbMsgMetaData metaData, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId) {
return newMsg(queueName, type, originator, null, metaData, data, ruleChainId, ruleNodeId);
}
public static TbMsg newMsg(String queueName, TbMsgType type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId) {
return new TbMsg(queueName, UUID.randomUUID(), System.currentTimeMillis(), type, originator, customerId,
metaData.copy(), TbMsgDataType.JSON, data, ruleChainId, ruleNodeId, null, TbMsgCallback.EMPTY);
}
public static TbMsg newMsg(TbMsgType type, EntityId originator, TbMsgMetaData metaData, String data) {
return newMsg(type, originator, null, metaData, data);
}
public static TbMsg newMsg(TbMsgType type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data) {
return new TbMsg(null, UUID.randomUUID(), System.currentTimeMillis(), type, originator, customerId,
metaData.copy(), TbMsgDataType.JSON, data, null, null, null, TbMsgCallback.EMPTY);
}
public static TbMsg newMsg(TbMsgType type, EntityId originator, TbMsgMetaData metaData, String data, long ts) {
return new TbMsg(null, UUID.randomUUID(), ts, type, originator, null,
metaData.copy(), TbMsgDataType.JSON, data, null, null, null, TbMsgCallback.EMPTY);
}
// REALLY NEW MSG
/**
* Creates a new TbMsg instance with the specified parameters.
*
* <p><strong>Deprecated:</strong> This method is deprecated since version 3.6.0 and should only be used when you need to
* specify a custom message type that doesn't exist in the {@link TbMsgType} enum. For standard message types,
* it is recommended to use the {@link #newMsg(String, TbMsgType, EntityId, TbMsgMetaData, String)}
* method instead.</p>
*
* @param queueName the name of the queue where the message will be sent
* @param type the type of the message
* @param originator the originator of the message
* @param metaData the metadata of the message
* @param data the data of the message
* @return new TbMsg instance
*/
@Deprecated(since = "3.6.0")
public static TbMsg newMsg(String queueName, String type, EntityId originator, TbMsgMetaData metaData, String data) {
return newMsg(queueName, type, originator, null, metaData, data);
}
/**
* Creates a new TbMsg instance with the specified parameters.
*
* <p><strong>Deprecated:</strong> This method is deprecated since version 3.6.0 and should only be used when you need to
* specify a custom message type that doesn't exist in the {@link TbMsgType} enum. For standard message types,
* it is recommended to use the {@link #newMsg(String, TbMsgType, EntityId, CustomerId, TbMsgMetaData, String)}
* method instead.</p>
*
* @param queueName the name of the queue where the message will be sent
* @param type the type of the message
* @param originator the originator of the message
* @param customerId the ID of the customer associated with the message
* @param metaData the metadata of the message
* @param data the data of the message
* @return new TbMsg instance
*/
@Deprecated(since = "3.6.0")
public static TbMsg newMsg(String queueName, String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data) {
return new TbMsg(queueName, UUID.randomUUID(), System.currentTimeMillis(), null, type, originator, customerId,
metaData.copy(), TbMsgDataType.JSON, data, null, null, null, TbMsgCallback.EMPTY);
}
@Deprecated(since = "3.6.0", forRemoval = true)
public static TbMsg newMsg(String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, TbMsgDataType dataType, String data) {
return new TbMsg(null, UUID.randomUUID(), System.currentTimeMillis(), null, type, originator, customerId,
metaData.copy(), dataType, data, null, null, null, TbMsgCallback.EMPTY);
}
/**
* Creates a new TbMsg instance with the specified parameters.
*
* <p><strong>Deprecated:</strong> This method is deprecated since version 3.6.0 and should only be used when you need to
* specify a custom message type that doesn't exist in the {@link TbMsgType} enum. For standard message types,
* it is recommended to use the {@link #newMsg(TbMsgType, EntityId, TbMsgMetaData, TbMsgDataType, String)}
* method instead.</p>
*
* @param type the type of the message
* @param originator the originator of the message
* @param metaData the metadata of the message
* @param dataType the dataType of the message
* @param data the data of the message
* @return new TbMsg instance
*/
@Deprecated(since = "3.6.0")
public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, TbMsgDataType dataType, String data) {
return newMsg(type, originator, null, metaData, dataType, data);
}
public static TbMsg newMsg(String queueName, TbMsgType type, EntityId originator, TbMsgMetaData metaData, String data) {
return newMsg(queueName, type, originator, null, metaData, data);
}
public static TbMsg newMsg(String queueName, TbMsgType type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data) {
return new TbMsg(queueName, UUID.randomUUID(), System.currentTimeMillis(), type, originator, customerId,
metaData.copy(), TbMsgDataType.JSON, data, null, null, null, TbMsgCallback.EMPTY);
}
public static TbMsg newMsg(TbMsgType type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, TbMsgDataType dataType, String data) {
return new TbMsg(null, UUID.randomUUID(), System.currentTimeMillis(), type, originator, customerId,
metaData.copy(), dataType, data, null, null, null, TbMsgCallback.EMPTY);
}
public static TbMsg newMsg(TbMsgType type, EntityId originator, TbMsgMetaData metaData, TbMsgDataType dataType, String data) {
return newMsg(type, originator, null, metaData, dataType, data);
}
// For Tests only
@Deprecated(since = "3.6.0", forRemoval = true)
public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, TbMsgDataType dataType, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId) {
return new TbMsg(null, UUID.randomUUID(), System.currentTimeMillis(), null, type, originator, null,
metaData.copy(), dataType, data, ruleChainId, ruleNodeId, null, TbMsgCallback.EMPTY);
}
@Deprecated(since = "3.6.0", forRemoval = true)
public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, String data, TbMsgCallback callback) {
return new TbMsg(null, UUID.randomUUID(), System.currentTimeMillis(), null, type, originator, null,
metaData.copy(), TbMsgDataType.JSON, data, null, null, null, callback);
}
/**
* Transforms an existing TbMsg instance by changing its message type, originator, metadata, and data.
*
* <p><strong>Deprecated:</strong> This method is deprecated since version 3.6.0 and should only be used when you need to
* specify a custom message type that doesn't exist in the {@link TbMsgType} enum. For standard message types,
* it is recommended to use the {@link #transformMsg(TbMsg, TbMsgType, EntityId, TbMsgMetaData, String)}
* method instead.</p>
*
*
* @param tbMsg the TbMsg instance to transform
* @param type the new message type
* @param originator the new originator
* @param metaData the new metadata
* @param data the new data
* @return the transformed TbMsg instance
*/
@Deprecated(since = "3.6.0")
public static TbMsg transformMsg(TbMsg tbMsg, String type, EntityId originator, TbMsgMetaData metaData, String data) {
return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, null, type, originator, tbMsg.customerId, metaData.copy(), tbMsg.dataType,
data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.correlationId, tbMsg.partition, tbMsg.ctx.copy(), tbMsg.callback);
} }
public static TbMsg newMsg(TbMsgType type, EntityId originator, TbMsgMetaData metaData, TbMsgDataType dataType, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { public TbMsgBuilder transform() {
return new TbMsg(null, UUID.randomUUID(), System.currentTimeMillis(), type, originator, null, return new TbMsgTransformer(this);
metaData.copy(), dataType, data, ruleChainId, ruleNodeId, null, TbMsgCallback.EMPTY);
} }
public static TbMsg newMsg(TbMsgType type, EntityId originator, TbMsgMetaData metaData, String data, TbMsgCallback callback) { public TbMsgBuilder copy() {
return new TbMsg(null, UUID.randomUUID(), System.currentTimeMillis(), type, originator, null, return new TbMsgBuilder(this);
metaData.copy(), TbMsgDataType.JSON, data, null, null, null, callback);
} }
public static TbMsg transformMsg(TbMsg tbMsg, TbMsgType type, EntityId originator, TbMsgMetaData metaData, String data) { public TbMsg transform(String queueName) {
return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, type, type.name(), originator, tbMsg.customerId, metaData.copy(), tbMsg.dataType, return transform()
data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.correlationId, tbMsg.partition, tbMsg.ctx.copy(), tbMsg.callback); .queueName(queueName)
.resetRuleNodeId()
.build();
} }
public static TbMsg transformMsgOriginator(TbMsg tbMsg, EntityId originatorId) { // used for enqueueForTellNext
return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.internalType, tbMsg.type, originatorId, tbMsg.getCustomerId(), tbMsg.metaData, tbMsg.dataType,
tbMsg.data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.correlationId, tbMsg.partition, tbMsg.ctx.copy(), tbMsg.getCallback());
}
public static TbMsg transformMsgData(TbMsg tbMsg, String data) {
return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.internalType, tbMsg.type, tbMsg.originator, tbMsg.customerId, tbMsg.metaData, tbMsg.dataType,
data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.correlationId, tbMsg.partition, tbMsg.ctx.copy(), tbMsg.getCallback());
}
public static TbMsg transformMsgMetadata(TbMsg tbMsg, TbMsgMetaData metadata) {
return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.internalType, tbMsg.type, tbMsg.originator, tbMsg.customerId, metadata.copy(), tbMsg.dataType,
tbMsg.data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.correlationId, tbMsg.partition, tbMsg.ctx.copy(), tbMsg.getCallback());
}
public static TbMsg transformMsg(TbMsg tbMsg, TbMsgMetaData metadata, String data) {
return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.internalType, tbMsg.type, tbMsg.originator, tbMsg.customerId, metadata, tbMsg.dataType,
data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.correlationId, tbMsg.partition, tbMsg.ctx.copy(), tbMsg.getCallback());
}
public static TbMsg transformMsgCustomerId(TbMsg tbMsg, CustomerId customerId) {
return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.internalType, tbMsg.type, tbMsg.originator, customerId, tbMsg.metaData, tbMsg.dataType,
tbMsg.data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.correlationId, tbMsg.partition, tbMsg.ctx.copy(), tbMsg.getCallback());
}
public static TbMsg transformMsgRuleChainId(TbMsg tbMsg, RuleChainId ruleChainId) {
return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.internalType, tbMsg.type, tbMsg.originator, tbMsg.customerId, tbMsg.metaData, tbMsg.dataType,
tbMsg.data, ruleChainId, null, tbMsg.correlationId, tbMsg.partition, tbMsg.ctx.copy(), tbMsg.getCallback());
}
public static TbMsg transformMsgQueueName(TbMsg tbMsg, String queueName) {
return new TbMsg(queueName, tbMsg.id, tbMsg.ts, tbMsg.internalType, tbMsg.type, tbMsg.originator, tbMsg.customerId, tbMsg.metaData, tbMsg.dataType,
tbMsg.data, tbMsg.getRuleChainId(), null, tbMsg.correlationId, tbMsg.partition, tbMsg.ctx.copy(), tbMsg.getCallback());
}
public static TbMsg transformMsg(TbMsg tbMsg, RuleChainId ruleChainId, String queueName) {
return new TbMsg(queueName, tbMsg.id, tbMsg.ts, tbMsg.internalType, tbMsg.type, tbMsg.originator, tbMsg.customerId, tbMsg.metaData, tbMsg.dataType,
tbMsg.data, ruleChainId, null, tbMsg.correlationId, tbMsg.partition, tbMsg.ctx.copy(), tbMsg.getCallback());
}
//used for enqueueForTellNext
public static TbMsg newMsg(TbMsg tbMsg, String queueName, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { public static TbMsg newMsg(TbMsg tbMsg, String queueName, RuleChainId ruleChainId, RuleNodeId ruleNodeId) {
return new TbMsg(queueName, UUID.randomUUID(), tbMsg.getTs(), tbMsg.getInternalType(), tbMsg.getType(), tbMsg.getOriginator(), tbMsg.customerId, tbMsg.getMetaData().copy(), return tbMsg.transform()
tbMsg.getDataType(), tbMsg.getData(), ruleChainId, ruleNodeId, tbMsg.correlationId, tbMsg.partition, tbMsg.ctx.copy(), TbMsgCallback.EMPTY); .id(UUID.randomUUID())
} .queueName(queueName)
.metaData(tbMsg.getMetaData())
private TbMsg(String queueName, UUID id, long ts, TbMsgType internalType, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, TbMsgDataType dataType, String data, .ruleChainId(ruleChainId)
RuleChainId ruleChainId, RuleNodeId ruleNodeId, TbMsgProcessingCtx ctx, TbMsgCallback callback) { .ruleNodeId(ruleNodeId)
this(queueName, id, ts, internalType, internalType.name(), originator, customerId, metaData, dataType, data, ruleChainId, ruleNodeId, ctx, callback); .callback(TbMsgCallback.EMPTY)
.build();
} }
private TbMsg(String queueName, UUID id, long ts, TbMsgType internalType, String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, TbMsgDataType dataType, String data, public TbMsg copyWithNewCtx() {
RuleChainId ruleChainId, RuleNodeId ruleNodeId, TbMsgProcessingCtx ctx, TbMsgCallback callback) { return copy()
this(queueName, id, ts, internalType, type, originator, customerId, metaData, dataType, data, ruleChainId, ruleNodeId, null, null, ctx, callback); .ctx(ctx.copy())
.callback(TbMsgCallback.EMPTY)
.build();
} }
private TbMsg(String queueName, UUID id, long ts, TbMsgType internalType, String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, TbMsgDataType dataType, String data, private TbMsg(String queueName, UUID id, long ts, TbMsgType internalType, String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, TbMsgDataType dataType, String data,
RuleChainId ruleChainId, RuleNodeId ruleNodeId, UUID correlationId, Integer partition, TbMsgProcessingCtx ctx, TbMsgCallback callback) { RuleChainId ruleChainId, RuleNodeId ruleNodeId, UUID correlationId, Integer partition, TbMsgProcessingCtx ctx, TbMsgCallback callback) {
this.id = id; this.id = id != null ? id : UUID.randomUUID();
this.queueName = queueName; this.queueName = queueName;
if (ts > 0) { if (ts > 0) {
this.ts = ts; this.ts = ts;
} else { } else {
this.ts = System.currentTimeMillis(); this.ts = System.currentTimeMillis();
} }
this.type = type;
this.internalType = internalType != null ? internalType : getInternalType(type); this.internalType = internalType != null ? internalType : getInternalType(type);
this.type = type != null ? type : this.internalType.name();
this.originator = originator; this.originator = originator;
if (customerId == null || customerId.isNullUid()) { if (customerId == null || customerId.isNullUid()) {
if (originator != null && originator.getEntityType() == EntityType.CUSTOMER) { if (originator != null && originator.getEntityType() == EntityType.CUSTOMER) {
@ -361,7 +133,7 @@ public final class TbMsg implements Serializable {
this.customerId = customerId; this.customerId = customerId;
} }
this.metaData = metaData; this.metaData = metaData;
this.dataType = dataType; this.dataType = dataType != null ? dataType : TbMsgDataType.JSON;
this.data = data; this.data = data;
this.ruleChainId = ruleChainId; this.ruleChainId = ruleChainId;
this.ruleNodeId = ruleNodeId; this.ruleNodeId = ruleNodeId;
@ -458,23 +230,8 @@ public final class TbMsg implements Serializable {
} }
} }
public TbMsg copyWithRuleChainId(RuleChainId ruleChainId) { public int getAndIncrementRuleNodeCounter() {
return copyWithRuleChainId(ruleChainId, this.id); return ctx.getAndIncrementRuleNodeCounter();
}
public TbMsg copyWithRuleChainId(RuleChainId ruleChainId, UUID msgId) {
return new TbMsg(this.queueName, msgId, this.ts, this.internalType, this.type, this.originator, this.customerId,
this.metaData, this.dataType, this.data, ruleChainId, null, this.correlationId, this.partition, this.ctx, callback);
}
public TbMsg copyWithRuleNodeId(RuleChainId ruleChainId, RuleNodeId ruleNodeId, UUID msgId) {
return new TbMsg(this.queueName, msgId, this.ts, this.internalType, this.type, this.originator, this.customerId,
this.metaData, this.dataType, this.data, ruleChainId, ruleNodeId, this.correlationId, this.partition, this.ctx, callback);
}
public TbMsg copyWithNewCtx() {
return new TbMsg(this.queueName, this.id, this.ts, this.internalType, this.type, this.originator, this.customerId,
this.metaData, this.dataType, this.data, ruleChainId, ruleNodeId, this.correlationId, this.partition, this.ctx.copy(), TbMsgCallback.EMPTY);
} }
public TbMsgCallback getCallback() { public TbMsgCallback getCallback() {
@ -510,11 +267,13 @@ public final class TbMsg implements Serializable {
} }
private TbMsgType getInternalType(String type) { private TbMsgType getInternalType(String type) {
try { if (type != null) {
return TbMsgType.valueOf(type); try {
} catch (IllegalArgumentException e) { return TbMsgType.valueOf(type);
return TbMsgType.NA; } catch (IllegalArgumentException ignored) {
}
} }
return TbMsgType.NA;
} }
public boolean isTypeOf(TbMsgType tbMsgType) { public boolean isTypeOf(TbMsgType tbMsgType) {
@ -530,4 +289,194 @@ public final class TbMsg implements Serializable {
return false; return false;
} }
public static class TbMsgTransformer extends TbMsgBuilder {
TbMsgTransformer(TbMsg tbMsg) {
super(tbMsg);
}
/*
* metadata is only copied if specified explicitly during transform
* */
@Override
public TbMsgTransformer metaData(TbMsgMetaData metaData) {
this.metaData = metaData.copy();
return this;
}
/*
* setting ruleNodeId to null when updating ruleChainId
* */
@Override
public TbMsgBuilder ruleChainId(RuleChainId ruleChainId) {
this.ruleChainId = ruleChainId;
this.ruleNodeId = null;
return this;
}
@Override
public TbMsg build() {
/*
* always copying ctx when transforming
* */
if (this.ctx != null) {
this.ctx = this.ctx.copy();
}
return super.build();
}
}
public static class TbMsgBuilder {
protected String queueName;
protected UUID id;
protected long ts;
protected String type;
protected TbMsgType internalType;
protected EntityId originator;
protected CustomerId customerId;
protected TbMsgMetaData metaData;
protected TbMsgDataType dataType;
protected String data;
protected RuleChainId ruleChainId;
protected RuleNodeId ruleNodeId;
protected UUID correlationId;
protected Integer partition;
protected TbMsgProcessingCtx ctx;
protected TbMsgCallback callback;
TbMsgBuilder() {}
TbMsgBuilder(TbMsg tbMsg) {
this.queueName = tbMsg.queueName;
this.id = tbMsg.id;
this.ts = tbMsg.ts;
this.type = tbMsg.type;
this.internalType = tbMsg.internalType;
this.originator = tbMsg.originator;
this.customerId = tbMsg.customerId;
this.metaData = tbMsg.metaData;
this.dataType = tbMsg.dataType;
this.data = tbMsg.data;
this.ruleChainId = tbMsg.ruleChainId;
this.ruleNodeId = tbMsg.ruleNodeId;
this.correlationId = tbMsg.correlationId;
this.partition = tbMsg.partition;
this.ctx = tbMsg.ctx;
this.callback = tbMsg.callback;
}
public TbMsgBuilder queueName(String queueName) {
this.queueName = queueName;
return this;
}
public TbMsgBuilder id(UUID id) {
this.id = id;
return this;
}
public TbMsgBuilder ts(long ts) {
this.ts = ts;
return this;
}
/**
* <p><strong>Deprecated:</strong> This should only be used when you need to specify a custom message type that doesn't exist in the {@link TbMsgType} enum.
* Prefer using {@link #type(TbMsgType)} instead.
*
* */
@Deprecated
public TbMsgBuilder type(String type) {
this.type = type;
this.internalType = null;
return this;
}
public TbMsgBuilder type(TbMsgType internalType) {
this.internalType = internalType;
this.type = internalType.name();
return this;
}
public TbMsgBuilder originator(EntityId originator) {
this.originator = originator;
return this;
}
public TbMsgBuilder customerId(CustomerId customerId) {
this.customerId = customerId;
return this;
}
public TbMsgBuilder metaData(TbMsgMetaData metaData) {
this.metaData = metaData;
return this;
}
public TbMsgBuilder copyMetaData(TbMsgMetaData metaData) {
this.metaData = metaData.copy();
return this;
}
public TbMsgBuilder dataType(TbMsgDataType dataType) {
this.dataType = dataType;
return this;
}
public TbMsgBuilder data(String data) {
this.data = data;
return this;
}
public TbMsgBuilder ruleChainId(RuleChainId ruleChainId) {
this.ruleChainId = ruleChainId;
return this;
}
public TbMsgBuilder ruleNodeId(RuleNodeId ruleNodeId) {
this.ruleNodeId = ruleNodeId;
return this;
}
public TbMsgBuilder resetRuleNodeId() {
return ruleNodeId(null);
}
public TbMsgBuilder correlationId(UUID correlationId) {
this.correlationId = correlationId;
return this;
}
public TbMsgBuilder partition(Integer partition) {
this.partition = partition;
return this;
}
public TbMsgBuilder ctx(TbMsgProcessingCtx ctx) {
this.ctx = ctx;
return this;
}
public TbMsgBuilder callback(TbMsgCallback callback) {
this.callback = callback;
return this;
}
public TbMsg build() {
return new TbMsg(queueName, id, ts, internalType, type, originator, customerId, metaData, dataType, data, ruleChainId, ruleNodeId, correlationId, partition, ctx, callback);
}
public String toString() {
return "TbMsg.TbMsgBuilder(queueName=" + this.queueName + ", id=" + this.id + ", ts=" + this.ts +
", type=" + this.type + ", internalType=" + this.internalType + ", originator=" + this.originator +
", customerId=" + this.customerId + ", metaData=" + this.metaData + ", dataType=" + this.dataType +
", data=" + this.data + ", ruleChainId=" + this.ruleChainId + ", ruleNodeId=" + this.ruleNodeId +
", correlationId=" + this.correlationId + ", partition=" + this.partition + ", ctx=" + this.ctx +
", callback=" + this.callback + ")";
}
}
} }

2
common/queue/src/main/java/org/thingsboard/server/queue/common/TbRuleEngineProducerService.java

@ -47,7 +47,7 @@ public class TbRuleEngineProducerService {
Integer partition = tpi.getPartition().orElse(null); Integer partition = tpi.getPartition().orElse(null);
UUID id = i > 0 ? UUID.randomUUID() : tbMsg.getId(); UUID id = i > 0 ? UUID.randomUUID() : tbMsg.getId();
tbMsg = tbMsg.toBuilder() tbMsg = tbMsg.transform()
.id(id) .id(id)
.correlationId(correlationId) .correlationId(correlationId)
.partition(partition) .partition(partition)

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

@ -45,7 +45,6 @@ import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.device.data.PowerMode; import org.thingsboard.server.common.data.device.data.PowerMode;
import org.thingsboard.server.common.data.exception.TenantNotFoundException;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.DeviceProfileId;
@ -1137,7 +1136,15 @@ public class DefaultTransportService extends TransportActivityManager implements
queueName = deviceProfile.getDefaultQueueName(); queueName = deviceProfile.getDefaultQueueName();
} }
TbMsg tbMsg = TbMsg.newMsg(queueName, tbMsgType, deviceId, customerId, metaData, gson.toJson(json), ruleChainId, null); TbMsg tbMsg = TbMsg.newMsg()
.queueName(queueName)
.type(tbMsgType)
.originator(deviceId)
.customerId(customerId)
.copyMetaData(metaData)
.data(gson.toJson(json))
.ruleChainId(ruleChainId)
.build();
ruleEngineProducerService.sendToRuleEngine(ruleEngineMsgProducer, tenantId, tbMsg, new StatsCallback(callback, ruleEngineProducerStats)); ruleEngineProducerService.sendToRuleEngine(ruleEngineMsgProducer, tenantId, tbMsg, new StatsCallback(callback, ruleEngineProducerStats));
ruleEngineProducerStats.incrementTotal(); ruleEngineProducerStats.incrementTotal();
} }

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

@ -51,7 +51,9 @@ import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.revwalk.filter.RevFilter; import org.eclipse.jgit.revwalk.filter.RevFilter;
import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.FetchResult; import org.eclipse.jgit.transport.FetchResult;
import org.eclipse.jgit.transport.PushResult;
import org.eclipse.jgit.transport.RefSpec; import org.eclipse.jgit.transport.RefSpec;
import org.eclipse.jgit.transport.RemoteRefUpdate;
import org.eclipse.jgit.transport.SshTransport; import org.eclipse.jgit.transport.SshTransport;
import org.eclipse.jgit.transport.URIish; import org.eclipse.jgit.transport.URIish;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
@ -85,6 +87,7 @@ import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.function.Function; import java.util.function.Function;
@ -350,8 +353,16 @@ public class GitRepository {
return; return;
} }
log.debug("Executing push [{}][{}]", settings.getRepositoryUri(), remoteBranch); log.debug("Executing push [{}][{}]", settings.getRepositoryUri(), remoteBranch);
execute(git.push() Iterable<PushResult> result = execute(git.push()
.setRefSpecs(new RefSpec(localBranch + ":" + remoteBranch))); .setRefSpecs(new RefSpec(localBranch + ":" + remoteBranch)));
result.forEach(pushResult -> {
for (RemoteRefUpdate update : pushResult.getRemoteUpdates()) {
RemoteRefUpdate.Status status = update.getStatus();
if (status != RemoteRefUpdate.Status.OK && status != RemoteRefUpdate.Status.UP_TO_DATE) {
throw new RuntimeException("Failed to push changes: " + Optional.ofNullable(update.getMessage()).orElseGet(status::name));
}
}
});
} }
public String getContentsDiff(String content1, String content2) throws IOException { public String getContentsDiff(String content1, String content2) throws IOException {

14
dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java

@ -101,14 +101,6 @@ public class BaseAttributesService implements AttributesService {
return attributesDao.save(tenantId, entityId, scope, attribute); return attributesDao.save(tenantId, entityId, scope, attribute);
} }
@Override
public ListenableFuture<List<Long>> save(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes) {
validate(entityId, scope);
AttributeUtils.validate(attributes, valueNoXssValidation);
List<ListenableFuture<Long>> saveFutures = attributes.stream().map(attribute -> attributesDao.save(tenantId, entityId, AttributeScope.valueOf(scope), attribute)).collect(Collectors.toList());
return Futures.allAsList(saveFutures);
}
@Override @Override
public ListenableFuture<List<Long>> save(TenantId tenantId, EntityId entityId, AttributeScope scope, List<AttributeKvEntry> attributes) { public ListenableFuture<List<Long>> save(TenantId tenantId, EntityId entityId, AttributeScope scope, List<AttributeKvEntry> attributes) {
validate(entityId, scope); validate(entityId, scope);
@ -117,12 +109,6 @@ public class BaseAttributesService implements AttributesService {
return Futures.allAsList(saveFutures); return Futures.allAsList(saveFutures);
} }
@Override
public ListenableFuture<List<String>> removeAll(TenantId tenantId, EntityId entityId, String scope, List<String> attributeKeys) {
validate(entityId, scope);
return Futures.allAsList(attributesDao.removeAll(tenantId, entityId, AttributeScope.valueOf(scope), attributeKeys));
}
@Override @Override
public ListenableFuture<List<String>> removeAll(TenantId tenantId, EntityId entityId, AttributeScope scope, List<String> attributeKeys) { public ListenableFuture<List<String>> removeAll(TenantId tenantId, EntityId entityId, AttributeScope scope, List<String> attributeKeys) {
validate(entityId, scope); validate(entityId, scope);

10
dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java

@ -222,11 +222,6 @@ public class CachedAttributesService implements AttributesService {
return doSave(tenantId, entityId, scope, attribute); return doSave(tenantId, entityId, scope, attribute);
} }
@Override
public ListenableFuture<List<Long>> save(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes) {
return save(tenantId, entityId, AttributeScope.valueOf(scope), attributes);
}
@Override @Override
public ListenableFuture<List<Long>> save(TenantId tenantId, EntityId entityId, AttributeScope scope, List<AttributeKvEntry> attributes) { public ListenableFuture<List<Long>> save(TenantId tenantId, EntityId entityId, AttributeScope scope, List<AttributeKvEntry> attributes) {
validate(entityId, scope); validate(entityId, scope);
@ -255,11 +250,6 @@ public class CachedAttributesService implements AttributesService {
log.trace("[{}][{}][{}] after cache put.", entityId, scope, key); log.trace("[{}][{}][{}] after cache put.", entityId, scope, key);
} }
@Override
public ListenableFuture<List<String>> removeAll(TenantId tenantId, EntityId entityId, String scope, List<String> attributeKeys) {
return removeAll(tenantId, entityId, AttributeScope.valueOf(scope), attributeKeys);
}
@Override @Override
public ListenableFuture<List<String>> removeAll(TenantId tenantId, EntityId entityId, AttributeScope scope, List<String> attributeKeys) { public ListenableFuture<List<String>> removeAll(TenantId tenantId, EntityId entityId, AttributeScope scope, List<String> attributeKeys) {
validate(entityId, scope); validate(entityId, scope);

6
dao/src/main/java/org/thingsboard/server/dao/domain/DomainServiceImpl.java

@ -35,6 +35,7 @@ import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent;
import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent;
import org.thingsboard.server.dao.oauth2.OAuth2ClientDao; import org.thingsboard.server.dao.oauth2.OAuth2ClientDao;
import org.thingsboard.server.dao.service.validator.DomainDataValidator;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
@ -53,17 +54,20 @@ public class DomainServiceImpl extends AbstractEntityService implements DomainSe
private OAuth2ClientDao oauth2ClientDao; private OAuth2ClientDao oauth2ClientDao;
@Autowired @Autowired
private DomainDao domainDao; private DomainDao domainDao;
@Autowired
private DomainDataValidator domainDataValidator;
@Override @Override
public Domain saveDomain(TenantId tenantId, Domain domain) { public Domain saveDomain(TenantId tenantId, Domain domain) {
log.trace("Executing saveDomain [{}]", domain); log.trace("Executing saveDomain [{}]", domain);
try { try {
domainDataValidator.validate(domain, Domain::getTenantId);
Domain savedDomain = domainDao.save(tenantId, domain); Domain savedDomain = domainDao.save(tenantId, domain);
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(tenantId).entityId(savedDomain.getId()).entity(savedDomain).build()); eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(tenantId).entityId(savedDomain.getId()).entity(savedDomain).build());
return savedDomain; return savedDomain;
} catch (Exception e) { } catch (Exception e) {
checkConstraintViolation(e, checkConstraintViolation(e,
Map.of("domain_unq_key", "Domain with such name and scheme already exists!")); Map.of("domain_name_key", "Domain with such name and scheme already exists!"));
throw e; throw e;
} }
} }

4
dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRequestService.java

@ -82,8 +82,8 @@ public class DefaultNotificationRequestService implements NotificationRequestSer
} }
@Override @Override
public List<NotificationRequest> findNotificationRequestsByRuleIdAndOriginatorEntityId(TenantId tenantId, NotificationRuleId ruleId, EntityId originatorEntityId) { public List<NotificationRequest> findNotificationRequestsByRuleIdAndOriginatorEntityIdAndStatus(TenantId tenantId, NotificationRuleId ruleId, EntityId originatorEntityId, NotificationRequestStatus status) {
return notificationRequestDao.findByRuleIdAndOriginatorEntityId(tenantId, ruleId, originatorEntityId); return notificationRequestDao.findByRuleIdAndOriginatorEntityIdAndStatus(tenantId, ruleId, originatorEntityId, status);
} }
@Override @Override

2
dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestDao.java

@ -40,7 +40,7 @@ public interface NotificationRequestDao extends Dao<NotificationRequest> {
List<NotificationRequestId> findIdsByRuleId(TenantId tenantId, NotificationRequestStatus requestStatus, NotificationRuleId ruleId); List<NotificationRequestId> findIdsByRuleId(TenantId tenantId, NotificationRequestStatus requestStatus, NotificationRuleId ruleId);
List<NotificationRequest> findByRuleIdAndOriginatorEntityId(TenantId tenantId, NotificationRuleId ruleId, EntityId originatorEntityId); List<NotificationRequest> findByRuleIdAndOriginatorEntityIdAndStatus(TenantId tenantId, NotificationRuleId ruleId, EntityId originatorEntityId, NotificationRequestStatus status);
PageData<NotificationRequest> findAllByStatus(NotificationRequestStatus status, PageLink pageLink); PageData<NotificationRequest> findAllByStatus(NotificationRequestStatus status, PageLink pageLink);

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

@ -27,6 +27,7 @@ import org.apache.commons.lang3.tuple.Pair;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.cache.CaffeineTbTransactionalCache;
import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
@ -526,9 +527,16 @@ public class BaseImageService extends BaseResourceService implements ImageServic
} }
@Override @Override
public void inlineImage(HasImage entity) { public <T extends HasImage> T inlineImage(T entity) {
log.trace("Executing inlineImage [{}] [{}] [{}]", entity.getTenantId(), entity.getClass().getSimpleName(), entity.getName()); log.trace("Executing inlineImage [{}] [{}] [{}]", entity.getTenantId(), entity.getClass().getSimpleName(), entity.getName());
if (StringUtils.isEmpty(entity.getImage())) {
return entity;
}
if (cache instanceof CaffeineTbTransactionalCache) {
entity = JacksonUtil.clone(entity); // cloning the entity to avoid updating the cached one
}
entity.setImage(inlineImage(entity.getTenantId(), "image", entity.getImage(), true)); entity.setImage(inlineImage(entity.getTenantId(), "image", entity.getImage(), true));
return entity;
} }
@Override @Override

15
dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java

@ -43,7 +43,10 @@ public abstract class DataValidator<D extends BaseData<?>> {
Pattern.compile("^[A-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[A-Z0-9.-]+\\.[A-Z]{2,}$", Pattern.CASE_INSENSITIVE); Pattern.compile("^[A-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[A-Z0-9.-]+\\.[A-Z]{2,}$", Pattern.CASE_INSENSITIVE);
private static final Pattern QUEUE_PATTERN = Pattern.compile("^[a-zA-Z0-9_.\\-]+$"); private static final Pattern QUEUE_PATTERN = Pattern.compile("^[a-zA-Z0-9_.\\-]+$");
private static final String DOMAIN_REGEX = "^(((?!-))(xn--|_)?[a-z0-9-]{0,61}[a-z0-9]{1,1}\\.)*(xn--)?([a-z0-9][a-z0-9\\-]{0,60}|[a-z0-9-]{1,30}\\.[a-z]{2,})$";
private static final Pattern DOMAIN_PATTERN = Pattern.compile(DOMAIN_REGEX);
private static final String LOCALHOST_REGEX = "^localhost(:\\d{1,5})?$";
private static final Pattern LOCALHOST_PATTERN = Pattern.compile(LOCALHOST_REGEX);
private static final String NAME = "name"; private static final String NAME = "name";
private static final String TOPIC = "topic"; private static final String TOPIC = "topic";
@ -171,4 +174,14 @@ public abstract class DataValidator<D extends BaseData<?>> {
} }
} }
public static boolean isValidDomain(String domainName) {
if (domainName == null) {
return false;
}
if (LOCALHOST_PATTERN.matcher(domainName).matches()) {
return true;
}
return DOMAIN_PATTERN.matcher(domainName).matches();
}
} }

32
dao/src/main/java/org/thingsboard/server/dao/service/validator/DomainDataValidator.java

@ -0,0 +1,32 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.service.validator;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.domain.Domain;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
@Component
public class DomainDataValidator extends AbstractHasOtaPackageValidator<Domain> {
@Override
protected void validateDataImpl(TenantId tenantId, Domain domain) {
if (!isValidDomain(domain.getName())) {
throw new IncorrectParameterException("Domain name " + domain.getName() + " is invalid");
}
}
}

4
dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRequestDao.java

@ -73,8 +73,8 @@ public class JpaNotificationRequestDao extends JpaAbstractDao<NotificationReques
} }
@Override @Override
public List<NotificationRequest> findByRuleIdAndOriginatorEntityId(TenantId tenantId, NotificationRuleId ruleId, EntityId originatorEntityId) { public List<NotificationRequest> findByRuleIdAndOriginatorEntityIdAndStatus(TenantId tenantId, NotificationRuleId ruleId, EntityId originatorEntityId, NotificationRequestStatus status) {
return DaoUtil.convertDataList(notificationRequestRepository.findAllByRuleIdAndOriginatorEntityIdAndOriginatorEntityType(ruleId.getId(), originatorEntityId.getId(), originatorEntityId.getEntityType())); return DaoUtil.convertDataList(notificationRequestRepository.findAllByRuleIdAndOriginatorEntityIdAndOriginatorEntityTypeAndStatus(ruleId.getId(), originatorEntityId.getId(), originatorEntityId.getEntityType(), status));
} }
@Override @Override

2
dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java

@ -54,7 +54,7 @@ public interface NotificationRequestRepository extends JpaRepository<Notificatio
List<UUID> findAllIdsByStatusAndRuleId(@Param("status") NotificationRequestStatus status, List<UUID> findAllIdsByStatusAndRuleId(@Param("status") NotificationRequestStatus status,
@Param("ruleId") UUID ruleId); @Param("ruleId") UUID ruleId);
List<NotificationRequestEntity> findAllByRuleIdAndOriginatorEntityIdAndOriginatorEntityType(UUID ruleId, UUID originatorEntityId, EntityType originatorEntityType); List<NotificationRequestEntity> findAllByRuleIdAndOriginatorEntityIdAndOriginatorEntityTypeAndStatus(UUID ruleId, UUID originatorEntityId, EntityType originatorEntityType, NotificationRequestStatus status);
Page<NotificationRequestEntity> findAllByStatus(NotificationRequestStatus status, Pageable pageable); Page<NotificationRequestEntity> findAllByStatus(NotificationRequestStatus status, Pageable pageable);

4
dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java

@ -254,11 +254,11 @@ public class BaseTimeseriesService implements TimeseriesService {
} }
@Override @Override
public ListenableFuture<Collection<String>> removeAllLatest(TenantId tenantId, EntityId entityId) { public ListenableFuture<List<String>> removeAllLatest(TenantId tenantId, EntityId entityId) {
validate(entityId); validate(entityId);
return Futures.transformAsync(this.findAllLatest(tenantId, entityId), latest -> { return Futures.transformAsync(this.findAllLatest(tenantId, entityId), latest -> {
if (latest != null && !latest.isEmpty()) { if (latest != null && !latest.isEmpty()) {
Collection<String> keys = latest.stream().map(TsKvEntry::getKey).collect(Collectors.toList()); List<String> keys = latest.stream().map(TsKvEntry::getKey).collect(Collectors.toList());
return Futures.transform(this.removeLatest(tenantId, entityId, keys), res -> keys, MoreExecutors.directExecutor()); return Futures.transform(this.removeLatest(tenantId, entityId, keys), res -> keys, MoreExecutors.directExecutor());
} else { } else {
return Futures.immediateFuture(Collections.emptyList()); return Futures.immediateFuture(Collections.emptyList());

2
dao/src/main/resources/sql/schema-entities.sql

@ -896,7 +896,7 @@ CREATE TABLE IF NOT EXISTS queue_stats (
); );
CREATE TABLE IF NOT EXISTS qr_code_settings ( CREATE TABLE IF NOT EXISTS qr_code_settings (
id uuid NOT NULL CONSTRAINT mobile_app_settings_pkey PRIMARY KEY, id uuid NOT NULL CONSTRAINT qr_code_settings_pkey PRIMARY KEY,
created_time bigint NOT NULL, created_time bigint NOT NULL,
tenant_id uuid NOT NULL, tenant_id uuid NOT NULL,
use_default_app boolean, use_default_app boolean,

2
dao/src/test/java/org/thingsboard/server/dao/service/DomainServiceTest.java

@ -90,7 +90,7 @@ public class DomainServiceTest extends AbstractServiceTest {
public void testGetTenantDomains() { public void testGetTenantDomains() {
List<Domain> domains = new ArrayList<>(); List<Domain> domains = new ArrayList<>();
for (int i = 0; i < 5; i++) { for (int i = 0; i < 5; i++) {
Domain oAuth2Client = constructDomain(TenantId.SYS_TENANT_ID, StringUtils.randomAlphabetic(5), true, false); Domain oAuth2Client = constructDomain(TenantId.SYS_TENANT_ID, StringUtils.randomAlphabetic(5).toLowerCase(), true, false);
Domain savedOauth2Client = domainService.saveDomain(SYSTEM_TENANT_ID, oAuth2Client); Domain savedOauth2Client = domainService.saveDomain(SYSTEM_TENANT_ID, oAuth2Client);
domains.add(savedOauth2Client); domains.add(savedOauth2Client);
} }

8
docker/docker-upgrade-tb.sh

@ -28,13 +28,7 @@ case $i in
esac esac
done done
if [[ -z "${FROM_VERSION// }" ]]; then fromVersion="${FROM_VERSION// }"
echo "--fromVersion parameter is invalid or unspecified!"
echo "Usage: docker-upgrade-tb.sh --fromVersion={VERSION}"
exit 1
else
fromVersion="${FROM_VERSION// }"
fi
set -e set -e

8
msa/tb-node/docker/start-tb-node.sh

@ -47,12 +47,8 @@ elif [ "$UPGRADE_TB" == "true" ]; then
echo "Starting ThingsBoard upgrade ..." echo "Starting ThingsBoard upgrade ..."
if [[ -z "${FROM_VERSION// }" ]]; then
echo "FROM_VERSION variable is invalid or unspecified!" fromVersion="${FROM_VERSION// }"
exit 1
else
fromVersion="${FROM_VERSION// }"
fi
exec java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.ThingsboardInstallApplication \ exec java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.ThingsboardInstallApplication \
-Dspring.jpa.hibernate.ddl-auto=none \ -Dspring.jpa.hibernate.ddl-auto=none \

8
msa/tb/docker/upgrade-tb.sh

@ -20,15 +20,23 @@ start-db.sh
CONF_FOLDER="${pkg.installFolder}/conf" CONF_FOLDER="${pkg.installFolder}/conf"
jarfile=${pkg.installFolder}/bin/${pkg.name}.jar jarfile=${pkg.installFolder}/bin/${pkg.name}.jar
configfile=${pkg.name}.conf configfile=${pkg.name}.conf
upgradeversion=${DATA_FOLDER}/.upgradeversion
source "${CONF_FOLDER}/${configfile}" source "${CONF_FOLDER}/${configfile}"
FROM_VERSION=`cat ${upgradeversion}`
echo "Starting ThingsBoard upgrade ..." echo "Starting ThingsBoard upgrade ..."
fromVersion="${FROM_VERSION// }"
java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.ThingsboardInstallApplication \ java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.ThingsboardInstallApplication \
-Dspring.jpa.hibernate.ddl-auto=none \ -Dspring.jpa.hibernate.ddl-auto=none \
-Dinstall.upgrade=true \ -Dinstall.upgrade=true \
-Dinstall.upgrade.from_version=${fromVersion} \
-Dlogging.config=/usr/share/thingsboard/bin/install/logback.xml \ -Dlogging.config=/usr/share/thingsboard/bin/install/logback.xml \
org.springframework.boot.loader.launch.PropertiesLauncher org.springframework.boot.loader.launch.PropertiesLauncher
echo "${pkg.upgradeVersion}" > ${upgradeversion}
stop-db.sh stop-db.sh

8
packaging/java/scripts/install/upgrade.sh

@ -28,13 +28,7 @@ case $i in
esac esac
done done
if [[ -z "${FROM_VERSION// }" ]]; then fromVersion="${FROM_VERSION// }"
echo "--fromVersion parameter is invalid or unspecified!"
echo "Usage: upgrade.sh --fromVersion={VERSION}"
exit 1
else
fromVersion="${FROM_VERSION// }"
fi
CONF_FOLDER=${pkg.installFolder}/conf CONF_FOLDER=${pkg.installFolder}/conf
configfile=${pkg.name}.conf configfile=${pkg.name}.conf

8
packaging/java/scripts/install/upgrade_dev_db.sh

@ -28,13 +28,7 @@ case $i in
esac esac
done done
if [[ -z "${FROM_VERSION// }" ]]; then fromVersion="${FROM_VERSION// }"
echo "--fromVersion parameter is invalid or unspecified!"
echo "Usage: upgrade_dev_db.sh --fromVersion={VERSION}"
exit 1
else
fromVersion="${FROM_VERSION// }"
fi
BASE=${project.basedir}/target BASE=${project.basedir}/target
CONF_FOLDER=${BASE}/conf CONF_FOLDER=${BASE}/conf

6
packaging/java/scripts/windows/upgrade.bat

@ -15,12 +15,6 @@ IF NOT "%1"=="" (
GOTO :loop GOTO :loop
) )
if not defined fromVersion (
echo "--fromVersion parameter is invalid or unspecified!"
echo "Usage: upgrade.bat --fromVersion {VERSION}"
exit /b 1
)
SET LOADER_PATH=%BASE%\conf,%BASE%\extensions SET LOADER_PATH=%BASE%\conf,%BASE%\extensions
SET SQL_DATA_FOLDER=%BASE%\data\sql SET SQL_DATA_FOLDER=%BASE%\data\sql
SET jarfile=%BASE%\lib\${pkg.name}.jar SET jarfile=%BASE%\lib\${pkg.name}.jar

23
pom.xml

@ -869,6 +869,29 @@
</execution> </execution>
</executions> </executions>
</plugin> </plugin>
<plugin>
<groupId>io.github.git-commit-id</groupId>
<artifactId>git-commit-id-maven-plugin</artifactId>
<version>5.0.0</version>
<executions>
<execution>
<id>get-the-git-infos</id>
<goals>
<goal>revision</goal>
</goals>
<phase>initialize</phase>
</execution>
</executions>
<configuration>
<generateGitPropertiesFile>true</generateGitPropertiesFile>
<generateGitPropertiesFilename>${project.build.outputDirectory}/git.properties</generateGitPropertiesFilename>
<!-- <includeOnlyProperties>-->
<!-- <includeOnlyProperty>^git.build.(time|version)$</includeOnlyProperty>-->
<!-- <includeOnlyProperty>^git.commit.id.(abbrev|full)$</includeOnlyProperty>-->
<!-- </includeOnlyProperties>-->
<commitIdGenerationMode>full</commitIdGenerationMode>
</configuration>
</plugin>
</plugins> </plugins>
</pluginManagement> </pluginManagement>
<plugins> <plugins>

117
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/AttributesDeleteRequest.java

@ -0,0 +1,117 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.rule.engine.api;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.SettableFuture;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import java.util.List;
@Getter
@ToString
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class AttributesDeleteRequest {
private final TenantId tenantId;
private final EntityId entityId;
private final AttributeScope scope;
private final List<String> keys;
private final boolean notifyDevice;
private final FutureCallback<Void> callback;
public static Builder builder() {
return new Builder();
}
public static class Builder {
private TenantId tenantId;
private EntityId entityId;
private AttributeScope scope;
private List<String> keys;
private boolean notifyDevice;
private FutureCallback<Void> callback;
Builder() {}
public Builder tenantId(TenantId tenantId) {
this.tenantId = tenantId;
return this;
}
public Builder entityId(EntityId entityId) {
this.entityId = entityId;
return this;
}
public Builder scope(AttributeScope scope) {
this.scope = scope;
return this;
}
@Deprecated
public Builder scope(String scope) {
try {
this.scope = AttributeScope.valueOf(scope);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Invalid attribute scope '" + scope + "'");
}
return this;
}
public Builder keys(List<String> keys) {
this.keys = keys;
return this;
}
public Builder notifyDevice(boolean notifyDevice) {
this.notifyDevice = notifyDevice;
return this;
}
public Builder callback(FutureCallback<Void> callback) {
this.callback = callback;
return this;
}
public Builder future(SettableFuture<Void> future) {
return callback(new FutureCallback<>() {
@Override
public void onSuccess(Void result) {
future.set(result);
}
@Override
public void onFailure(Throwable t) {
future.setException(t);
}
});
}
public AttributesDeleteRequest build() {
return new AttributesDeleteRequest(tenantId, entityId, scope, keys, notifyDevice, callback);
}
}
}

128
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/AttributesSaveRequest.java

@ -0,0 +1,128 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.rule.engine.api;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.SettableFuture;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.KvEntry;
import java.util.List;
@Getter
@ToString
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class AttributesSaveRequest {
private final TenantId tenantId;
private final EntityId entityId;
private final AttributeScope scope;
private final List<AttributeKvEntry> entries;
private final boolean notifyDevice;
private final FutureCallback<Void> callback;
public static Builder builder() {
return new Builder();
}
public static class Builder {
private TenantId tenantId;
private EntityId entityId;
private AttributeScope scope;
private List<AttributeKvEntry> entries;
private boolean notifyDevice = true;
private FutureCallback<Void> callback;
Builder() {}
public Builder tenantId(TenantId tenantId) {
this.tenantId = tenantId;
return this;
}
public Builder entityId(EntityId entityId) {
this.entityId = entityId;
return this;
}
public Builder scope(AttributeScope scope) {
this.scope = scope;
return this;
}
@Deprecated
public Builder scope(String scope) {
try {
this.scope = AttributeScope.valueOf(scope);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Invalid attribute scope '" + scope + "'");
}
return this;
}
public Builder entries(List<AttributeKvEntry> entries) {
this.entries = entries;
return this;
}
public Builder entry(AttributeKvEntry entry) {
return entries(List.of(entry));
}
public Builder entry(KvEntry kvEntry) {
return entry(new BaseAttributeKvEntry(kvEntry, System.currentTimeMillis()));
}
public Builder notifyDevice(boolean notifyDevice) {
this.notifyDevice = notifyDevice;
return this;
}
public Builder callback(FutureCallback<Void> callback) {
this.callback = callback;
return this;
}
public Builder future(SettableFuture<Void> future) {
return callback(new FutureCallback<>() {
@Override
public void onSuccess(Void result) {
future.set(result);
}
@Override
public void onFailure(Throwable t) {
future.setException(t);
}
});
}
public AttributesSaveRequest build() {
return new AttributesSaveRequest(tenantId, entityId, scope, entries, notifyDevice, callback);
}
}
}

88
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java

@ -15,97 +15,17 @@
*/ */
package org.thingsboard.rule.engine.api; package org.thingsboard.rule.engine.api;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import java.util.Collection;
import java.util.List;
/** /**
* Created by ashvayka on 02.04.18. * Created by ashvayka on 02.04.18.
*/ */
public interface RuleEngineTelemetryService { public interface RuleEngineTelemetryService {
ListenableFuture<Void> saveAndNotify(TenantId tenantId, EntityId entityId, TsKvEntry ts); void saveTimeseries(TimeseriesSaveRequest request);
void saveAndNotify(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, FutureCallback<Void> callback);
void saveAndNotify(TenantId tenantId, CustomerId id, EntityId entityId, List<TsKvEntry> ts, long ttl, FutureCallback<Void> callback);
void saveWithoutLatestAndNotify(TenantId tenantId, CustomerId id, EntityId entityId, List<TsKvEntry> ts, long ttl, FutureCallback<Void> callback);
@Deprecated(since = "3.7.0")
void saveAndNotify(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, FutureCallback<Void> callback);
void saveAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, List<AttributeKvEntry> attributes, FutureCallback<Void> callback);
@Deprecated(since = "3.7.0")
void saveAndNotify(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, boolean notifyDevice, FutureCallback<Void> callback);
void saveAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, List<AttributeKvEntry> attributes, boolean notifyDevice, FutureCallback<Void> callback);
void saveLatestAndNotify(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, FutureCallback<Void> callback);
@Deprecated(since = "3.7.0")
ListenableFuture<Void> saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, long value);
ListenableFuture<Void> saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, long value);
@Deprecated(since = "3.7.0")
ListenableFuture<Void> saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, String value);
ListenableFuture<Void> saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, String value);
@Deprecated(since = "3.7.0")
ListenableFuture<Void> saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, double value);
ListenableFuture<Void> saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, double value);
@Deprecated(since = "3.7.0")
ListenableFuture<Void> saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, boolean value);
ListenableFuture<Void> saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, boolean value);
@Deprecated(since = "3.7.0")
void saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, long value, FutureCallback<Void> callback);
void saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, long value, FutureCallback<Void> callback);
@Deprecated(since = "3.7.0")
void saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, String value, FutureCallback<Void> callback);
void saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, String value, FutureCallback<Void> callback);
@Deprecated(since = "3.7.0")
void saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, double value, FutureCallback<Void> callback);
void saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, double value, FutureCallback<Void> callback);
@Deprecated(since = "3.7.0")
void saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, boolean value, FutureCallback<Void> callback);
void saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, boolean value, FutureCallback<Void> callback);
@Deprecated(since = "3.7.0")
void deleteAndNotify(TenantId tenantId, EntityId entityId, String scope, List<String> keys, FutureCallback<Void> callback);
void deleteAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, List<String> keys, FutureCallback<Void> callback);
@Deprecated(since = "3.7.0")
void deleteAndNotify(TenantId tenantId, EntityId entityId, String scope, List<String> keys, boolean notifyDevice, FutureCallback<Void> callback);
void deleteAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, List<String> keys, boolean notifyDevice, FutureCallback<Void> callback); void saveAttributes(AttributesSaveRequest request);
void deleteLatest(TenantId tenantId, EntityId entityId, List<String> keys, FutureCallback<Void> callback); void deleteTimeseries(TimeseriesDeleteRequest request);
void deleteAllLatest(TenantId tenantId, EntityId entityId, FutureCallback<Collection<String>> callback); void deleteAttributes(AttributesDeleteRequest request);
void deleteTimeseriesAndNotify(TenantId tenantId, EntityId entityId, List<String> keys, List<DeleteTsKvQuery> deleteTsKvQueries, FutureCallback<Void> callback);
} }

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

@ -192,9 +192,6 @@ public interface TbContext {
void ack(TbMsg tbMsg); void ack(TbMsg tbMsg);
@Deprecated(since = "3.6.0", forRemoval = true)
TbMsg newMsg(String queueName, String type, EntityId originator, TbMsgMetaData metaData, String data);
/** /**
* Creates a new TbMsg instance with the specified parameters. * Creates a new TbMsg instance with the specified parameters.
* *

85
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TimeseriesDeleteRequest.java

@ -0,0 +1,85 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.rule.engine.api;
import com.google.common.util.concurrent.FutureCallback;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
import java.util.List;
@Getter
@ToString
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class TimeseriesDeleteRequest {
private final TenantId tenantId;
private final EntityId entityId;
private final List<String> keys;
private final List<DeleteTsKvQuery> deleteHistoryQueries;
private final FutureCallback<List<String>> callback;
public static Builder builder() {
return new Builder();
}
public static class Builder {
private TenantId tenantId;
private EntityId entityId;
private List<String> keys;
private List<DeleteTsKvQuery> deleteHistoryQueries;
private FutureCallback<List<String>> callback;
Builder() {}
public Builder tenantId(TenantId tenantId) {
this.tenantId = tenantId;
return this;
}
public Builder entityId(EntityId entityId) {
this.entityId = entityId;
return this;
}
public Builder keys(List<String> keys) {
this.keys = keys;
return this;
}
public Builder deleteHistoryQueries(List<DeleteTsKvQuery> deleteHistoryQueries) {
this.deleteHistoryQueries = deleteHistoryQueries;
return this;
}
public Builder callback(FutureCallback<List<String>> callback) {
this.callback = callback;
return this;
}
public TimeseriesDeleteRequest build() {
return new TimeseriesDeleteRequest(tenantId, entityId, keys, deleteHistoryQueries, callback);
}
}
}

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

Loading…
Cancel
Save