Browse Source

merged with master

pull/10591/head
dashevchenko 2 years ago
parent
commit
3e0fad4a56
  1. 14
      application/src/main/data/json/system/widget_bundles/charts.json
  2. 6
      application/src/main/data/json/system/widget_bundles/status_indicators.json
  3. 30
      application/src/main/data/json/system/widget_types/bars.json
  4. 29
      application/src/main/data/json/system/widget_types/bars_deprecated.json
  5. 2
      application/src/main/data/json/system/widget_types/doughnut.json
  6. 2
      application/src/main/data/json/system/widget_types/horizontal_doughnut.json
  7. 26
      application/src/main/data/json/system/widget_types/pie.json
  8. 6
      application/src/main/data/json/system/widget_types/pie___chart_js.json
  9. 6
      application/src/main/data/json/system/widget_types/pie___flot.json
  10. 37
      application/src/main/data/json/system/widget_types/polar_area.json
  11. 25
      application/src/main/data/json/system/widget_types/polar_area_deprecated.json
  12. 33
      application/src/main/data/json/system/widget_types/radar.json
  13. 25
      application/src/main/data/json/system/widget_types/radar_deprecated.json
  14. 26
      application/src/main/data/json/system/widget_types/status_widget.json
  15. 25
      application/src/main/java/org/apache/kafka/common/network/NetworkReceive.java
  16. 2
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
  17. 6
      application/src/main/java/org/thingsboard/server/config/TbRuleEngineSecurityConfiguration.java
  18. 9
      application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java
  19. 2
      application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java
  20. 5
      application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
  21. 2
      application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
  22. 5
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  23. 2
      application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java
  24. 14
      application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java
  25. 11
      application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java
  26. 12
      application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java
  27. 16
      application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java
  28. 2
      application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java
  29. 1
      application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java
  30. 1
      application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java
  31. 118
      application/src/main/java/org/thingsboard/server/service/housekeeper/HousekeeperReprocessingService.java
  32. 166
      application/src/main/java/org/thingsboard/server/service/housekeeper/HousekeeperService.java
  33. 103
      application/src/main/java/org/thingsboard/server/service/housekeeper/InMemoryHouseKeeperServiceService.java
  34. 80
      application/src/main/java/org/thingsboard/server/service/housekeeper/processor/AlarmsDeletionTaskProcessor.java
  35. 47
      application/src/main/java/org/thingsboard/server/service/housekeeper/processor/AlarmsUnassignTaskProcessor.java
  36. 43
      application/src/main/java/org/thingsboard/server/service/housekeeper/processor/AttributesDeletionTaskProcessor.java
  37. 57
      application/src/main/java/org/thingsboard/server/service/housekeeper/processor/EntitiesDeletionTaskProcessor.java
  38. 40
      application/src/main/java/org/thingsboard/server/service/housekeeper/processor/EventsDeletionTaskProcessor.java
  39. 32
      application/src/main/java/org/thingsboard/server/service/housekeeper/processor/HousekeeperTaskProcessor.java
  40. 45
      application/src/main/java/org/thingsboard/server/service/housekeeper/processor/LatestTsDeletionTaskProcessor.java
  41. 60
      application/src/main/java/org/thingsboard/server/service/housekeeper/processor/TelemetryDeletionTaskProcessor.java
  42. 63
      application/src/main/java/org/thingsboard/server/service/housekeeper/processor/TenantEntitiesDeletionTaskProcessor.java
  43. 48
      application/src/main/java/org/thingsboard/server/service/housekeeper/processor/TsHistoryDeletionTaskProcessor.java
  44. 125
      application/src/main/java/org/thingsboard/server/service/housekeeper/stats/HousekeeperStatsService.java
  45. 37
      application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
  46. 2
      application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java
  47. 6
      application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java
  48. 3
      application/src/main/java/org/thingsboard/server/service/notification/channels/MobileAppNotificationChannel.java
  49. 2
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmAssignmentTriggerProcessor.java
  50. 2
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmCommentTriggerProcessor.java
  51. 2
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmTriggerProcessor.java
  52. 2
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/DeviceActivityTriggerProcessor.java
  53. 2
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EdgeCommunicationFailureTriggerProcessor.java
  54. 2
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EdgeConnectionTriggerProcessor.java
  55. 2
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EntitiesLimitTriggerProcessor.java
  56. 4
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/RuleEngineComponentLifecycleEventTriggerProcessor.java
  57. 53
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/TaskProcessingFailureTriggerProcessor.java
  58. 109
      application/src/main/java/org/thingsboard/server/service/queue/consumer/QueueConsumerManager.java
  59. 5
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/DefaultExportableEntitiesService.java
  60. 2
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DashboardExportService.java
  61. 2
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/NotificationTargetImportService.java
  62. 1
      application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java
  63. 7
      application/src/main/java/org/thingsboard/server/service/ttl/AuditLogsCleanUpService.java
  64. 2
      application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java
  65. 55
      application/src/main/resources/thingsboard.yml
  66. 41
      application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
  67. 115
      application/src/test/java/org/thingsboard/server/controller/AlarmControllerTest.java
  68. 24
      application/src/test/java/org/thingsboard/server/controller/AssetControllerTest.java
  69. 5
      application/src/test/java/org/thingsboard/server/controller/AssetProfileControllerTest.java
  70. 86
      application/src/test/java/org/thingsboard/server/controller/AuditLogControllerTest.java
  71. 5
      application/src/test/java/org/thingsboard/server/controller/ComponentDescriptorControllerTest.java
  72. 19
      application/src/test/java/org/thingsboard/server/controller/CustomerControllerTest.java
  73. 20
      application/src/test/java/org/thingsboard/server/controller/DashboardControllerTest.java
  74. 5
      application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java
  75. 18
      application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java
  76. 5
      application/src/test/java/org/thingsboard/server/controller/DeviceProfileControllerTest.java
  77. 5
      application/src/test/java/org/thingsboard/server/controller/EdgeControllerTest.java
  78. 75
      application/src/test/java/org/thingsboard/server/controller/EdgeEventControllerTest.java
  79. 169
      application/src/test/java/org/thingsboard/server/controller/EntityQueryControllerTest.java
  80. 5
      application/src/test/java/org/thingsboard/server/controller/EntityRelationControllerTest.java
  81. 7
      application/src/test/java/org/thingsboard/server/controller/EntityViewControllerTest.java
  82. 4
      application/src/test/java/org/thingsboard/server/controller/HomePageApiTest.java
  83. 7
      application/src/test/java/org/thingsboard/server/controller/OtaPackageControllerTest.java
  84. 5
      application/src/test/java/org/thingsboard/server/controller/RpcControllerTest.java
  85. 11
      application/src/test/java/org/thingsboard/server/controller/RuleChainControllerTest.java
  86. 5
      application/src/test/java/org/thingsboard/server/controller/TbResourceControllerTest.java
  87. 49
      application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java
  88. 39
      application/src/test/java/org/thingsboard/server/controller/TenantProfileControllerTest.java
  89. 17
      application/src/test/java/org/thingsboard/server/controller/UserControllerTest.java
  90. 24
      application/src/test/java/org/thingsboard/server/controller/WidgetTypeControllerTest.java
  91. 5
      application/src/test/java/org/thingsboard/server/controller/WidgetsBundleControllerTest.java
  92. 4
      application/src/test/java/org/thingsboard/server/edge/TenantEdgeTest.java
  93. 7
      application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java
  94. 2
      application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java
  95. 478
      application/src/test/java/org/thingsboard/server/service/housekeeper/HousekeeperServiceTest.java
  96. 23
      application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java
  97. 7
      application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java
  98. 6
      application/src/test/java/org/thingsboard/server/service/notification/NotificationTargetApiTest.java
  99. 16
      application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java
  100. 10
      application/src/test/java/org/thingsboard/server/service/sql/SequentialTimeseriesPersistenceTest.java

14
application/src/main/data/json/system/widget_bundles/charts.json

@ -15,17 +15,21 @@
"state_chart",
"bar_chart_with_labels",
"range_chart",
"cards.aggregated_value_card",
"bars",
"pie",
"doughnut",
"horizontal_doughnut",
"polar_area",
"radar",
"charts.basic_timeseries",
"charts.state_chart",
"charts.timeseries_bars_flot",
"cards.aggregated_value_card",
"charts.bars",
"charts.pie",
"charts.pie_chart_js",
"doughnut",
"horizontal_doughnut",
"charts.doughnut_chart_js",
"charts.polar_area_chart_js",
"charts.radar_chart_js",
"charts.doughnut_chart_js"
"charts.radar_chart_js"
]
}

6
application/src/main/data/json/system/widget_bundles/status_indicators.json

@ -2,15 +2,15 @@
"widgetsBundle": {
"alias": "status_indicators",
"title": "Status indicators",
"image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAMAAAB+IdObAAAAjVBMVEUAAADu7u7u7u7g4OD///9c35Dw8PCt78fz/ffW9+PHx8dw457r+/Hj4+Pl+u6QkJDZ+OWsrKzM9tzf+er4/vuE56xYWFi6urp0dHTV1dXS9uCZ67qCgoKF56tm4Zeenp7C89VmZmZKSkq48c7A89Wj7cFLS0s9PT08PDyF56zi+euP6bN65aWZ67lZWVkXV4nvAAAABHRSTlMA799f7FlksgAABdJJREFUeNrt3I12mjAYBuBt/YAkJCJgCVQEFLX+7v4vbwHGQEnbeQwOXF7XVjjuvDxiCN04fHv5bvaaHy/fRB7Q8gN6znfheESLCT3HFI4HtGiIhlTREA0ZJSQi5ZJ6iKSlV8i7u4qAAYTiUfwEUzVE3qIeQkKWOku+4zvixEuydZcsVA2Rt6iGxCR1VhCIx9YJiANhylVD5C2qIUu+5StYsZ9sSVxRQXagGiJvUQvhbgSMc+CMRSYnplgKVUPkLb0ffmOmGiJv0fOIhmjIVTREQ54FMquiGiJv6REy8aocFEPkLdPeILM373dQj5CZVweph8wmvojXZCoWD0ghpGl5u2qhCiEIT5Ak04lSCJ3SWTfIPyiDzKazzJKE+kghZPZG90Y3J+9AVUHoJDNkOc6mCiET+iptSdBUFWSCLGnFAt4UQnxkyIM1REM0RENuhXjShvn4ICCbEHM0QgjCksD4IDSRNeTjgzzNYNcQDflHEJNzgDRIwYwD3h+EcAAeugBOyPuBLAMHyNYMiBuznQnAUrMHSBiEwHaRaFlGZYtr3grJv4CA64AbgpOGHLYMII7cVD2kqOCixQldCCIAJ0rTGyHobHezOHQgTkggEJDIiVk/ELeGkKLFYTdCQJ5rSMxDt9wjwGLH7W+PxBUEmHh2G4Tu7W5O/gXEXDkrRt7jFUC5151eIOYqfWdkm+5AJCVuqGKMHBsIY+KLmNW3MsRUCmlaXAIQ1cOcMz2PaIiGaMgzQ6gta7DGBwFEu0Ej/J0drHk3Gzo+SCaf2ccHeZrBriEaoiGfQuizQCZY2jBHU4WQA32VQ6gyCOCZJ/1vhSlVCEFT6YlQktOJMgiaTii6Dp1gtVc+THw0k2SCVV5UQyeSzEApBJA/lYTqC88uoyEaoiHtlu/Qc34IxyNaXn6Yveb7i3A8okVHR+fT6DsM/GX0HQZuylPN7BqiIWU05P+CmBGDCJowAvK4d0DUtsghP93A5GAWq8QXA+KUz0hafC/Xln9CCMw7IEpb5JClG5kBC+LQWa4ix4mLimWcxgHZ8jhO3SAgcfwe7cjWCW6E9N9yoRbvVcBTFjrEIakTFBVxGBGxaO6cpSsSQP24BdJfixyyIksSkNAtK1Z8VVYUa7gDK0JckSV5hy27A6K4RQ5hnAABnsYRi5hYMKPq6jvOouInEyHuFqKIALkR0n+L2VlVXwsnDQ9dFYffr1v0PNJEQzREQzREQzSkjI9F6PghuVEGK4Egf51v5kU21hqjh0JeK4h1N8TPj7ZxmWRv4ZFB8CYx5EmO2WggyGoUUsucjgFC58bX2eOhQySMxLbtpEuhg4ZYSVuw2KwP9fYi7FmLC8+cDhaC7ZZig5HsINB6iZ0NFJK3LprD8FEO56TZKUgtBPlY5E4I2v/ZGRaCz0Iz+89OoUohdjX87oJQW/o2y4OsP2isEIKNKndBascJX5+jFNfLW5aH0YX7XEvQsCC+UWVzMbL3RjunuQdNst9DJRsWBFUfefzFSUoyx9cfRjwsCFgXQxfvO4buQRcVH6/z0MYIeFaO6ve6YUgptPk7GAYHaZInl9ttn2z7krJBUGe4kPbuOG3WtF6NrUV7pwwe4tvNvEg/ngmTTBUEW0WoaghNPj+Raii5Ggj9ve9VQ46SsxQ5xVYBaTa+H8iJwsehxzFAMkPk3NodyMsty8q9Ns0qG4YNgWxxyqEOtezmQDXH7RdZMHBIK/6+cy9FaDIaCNoY3czp6CDUlp+e+CODtB2JnbQW/HFBvKt/g8B5LdvcD0G5JZI9BLKu58XOTGjdD5kbZbxHQFCx1QvaHf42vRfSbONjBvvmnHcnmfOGwsggH2a8ELS2MvoEkPIXFNsbPaSeUPyxQ7J6+hg75GxUWYwdkhtVjmOHHIwq2dghYFVDZPRHLYDDcXH0nmAe6WFmt1qfVvTabC9Kyuf2xYuGDAFvLYKhE7ougtovGv9FNc9zmZOGaIiG3Bl9h4EB3mHgF3NeY+W3xB1xAAAAAElFTkSuQmCC",
"image": "tb-image:c3RhdHVzX2luZGljYXRvcnNfc3lzdGVtX2J1bmRsZV9pbWFnZS5wbmc=:IlN0YXR1cyBpbmRpY2F0b3JzIiBzeXN0ZW0gYnVuZGxlIGltYWdl;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAMAAAB+IdObAAAAjVBMVEUAAADu7u7u7u7g4OD///9c35Dw8PCt78fz/ffW9+PHx8dw457r+/Hj4+Pl+u6QkJDZ+OWsrKzM9tzf+er4/vuE56xYWFi6urp0dHTV1dXS9uCZ67qCgoKF56tm4Zeenp7C89VmZmZKSkq48c7A89Wj7cFLS0s9PT08PDyF56zi+euP6bN65aWZ67lZWVkXV4nvAAAABHRSTlMA799f7FlksgAABdJJREFUeNrt3I12mjAYBuBt/YAkJCJgCVQEFLX+7v4vbwHGQEnbeQwOXF7XVjjuvDxiCN04fHv5bvaaHy/fRB7Q8gN6znfheESLCT3HFI4HtGiIhlTREA0ZJSQi5ZJ6iKSlV8i7u4qAAYTiUfwEUzVE3qIeQkKWOku+4zvixEuydZcsVA2Rt6iGxCR1VhCIx9YJiANhylVD5C2qIUu+5StYsZ9sSVxRQXagGiJvUQvhbgSMc+CMRSYnplgKVUPkLb0ffmOmGiJv0fOIhmjIVTREQ54FMquiGiJv6REy8aocFEPkLdPeILM373dQj5CZVweph8wmvojXZCoWD0ghpGl5u2qhCiEIT5Ak04lSCJ3SWTfIPyiDzKazzJKE+kghZPZG90Y3J+9AVUHoJDNkOc6mCiET+iptSdBUFWSCLGnFAt4UQnxkyIM1REM0RENuhXjShvn4ICCbEHM0QgjCksD4IDSRNeTjgzzNYNcQDflHEJNzgDRIwYwD3h+EcAAeugBOyPuBLAMHyNYMiBuznQnAUrMHSBiEwHaRaFlGZYtr3grJv4CA64AbgpOGHLYMII7cVD2kqOCixQldCCIAJ0rTGyHobHezOHQgTkggEJDIiVk/ELeGkKLFYTdCQJ5rSMxDt9wjwGLH7W+PxBUEmHh2G4Tu7W5O/gXEXDkrRt7jFUC5151eIOYqfWdkm+5AJCVuqGKMHBsIY+KLmNW3MsRUCmlaXAIQ1cOcMz2PaIiGaMgzQ6gta7DGBwFEu0Ej/J0drHk3Gzo+SCaf2ccHeZrBriEaoiGfQuizQCZY2jBHU4WQA32VQ6gyCOCZJ/1vhSlVCEFT6YlQktOJMgiaTii6Dp1gtVc+THw0k2SCVV5UQyeSzEApBJA/lYTqC88uoyEaoiHtlu/Qc34IxyNaXn6Yveb7i3A8okVHR+fT6DsM/GX0HQZuylPN7BqiIWU05P+CmBGDCJowAvK4d0DUtsghP93A5GAWq8QXA+KUz0hafC/Xln9CCMw7IEpb5JClG5kBC+LQWa4ix4mLimWcxgHZ8jhO3SAgcfwe7cjWCW6E9N9yoRbvVcBTFjrEIakTFBVxGBGxaO6cpSsSQP24BdJfixyyIksSkNAtK1Z8VVYUa7gDK0JckSV5hy27A6K4RQ5hnAABnsYRi5hYMKPq6jvOouInEyHuFqKIALkR0n+L2VlVXwsnDQ9dFYffr1v0PNJEQzREQzREQzSkjI9F6PghuVEGK4Egf51v5kU21hqjh0JeK4h1N8TPj7ZxmWRv4ZFB8CYx5EmO2WggyGoUUsucjgFC58bX2eOhQySMxLbtpEuhg4ZYSVuw2KwP9fYi7FmLC8+cDhaC7ZZig5HsINB6iZ0NFJK3LprD8FEO56TZKUgtBPlY5E4I2v/ZGRaCz0Iz+89OoUohdjX87oJQW/o2y4OsP2isEIKNKndBascJX5+jFNfLW5aH0YX7XEvQsCC+UWVzMbL3RjunuQdNst9DJRsWBFUfefzFSUoyx9cfRjwsCFgXQxfvO4buQRcVH6/z0MYIeFaO6ve6YUgptPk7GAYHaZInl9ttn2z7krJBUGe4kPbuOG3WtF6NrUV7pwwe4tvNvEg/ngmTTBUEW0WoaghNPj+Raii5Ggj9ve9VQ46SsxQ5xVYBaTa+H8iJwsehxzFAMkPk3NodyMsty8q9Ns0qG4YNgWxxyqEOtezmQDXH7RdZMHBIK/6+cy9FaDIaCNoY3czp6CDUlp+e+CODtB2JnbQW/HFBvKt/g8B5LdvcD0G5JZI9BLKu58XOTGjdD5kbZbxHQFCx1QvaHf42vRfSbONjBvvmnHcnmfOGwsggH2a8ELS2MvoEkPIXFNsbPaSeUPyxQ7J6+hg75GxUWYwdkhtVjmOHHIwq2dghYFVDZPRHLYDDcXH0nmAe6WFmt1qfVvTabC9Kyuf2xYuGDAFvLYKhE7ougtovGv9FNc9zmZOGaIiG3Bl9h4EB3mHgF3NeY+W3xB1xAAAAAElFTkSuQmCC",
"description": "Contains widgets displaying battery level and signal strength.",
"order": 9000,
"externalId": null,
"name": "Status indicators"
},
"widgetTypeFqns": [
"battery_level",
"signal_strength",
"progress_bar"
"progress_bar",
"status_widget"
]
}

30
application/src/main/data/json/system/widget_types/bars.json

File diff suppressed because one or more lines are too long

29
application/src/main/data/json/system/widget_types/bars_deprecated.json

@ -0,0 +1,29 @@
{
"fqn": "charts.bars",
"name": "Bars",
"deprecated": true,
"image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAMAAAB+IdObAAAA8FBMVEUhlvNMr1Bqamp5eXl7e3t8fHx9fX1+fn5/f3+AgICCgoKDg4OEhISGhoaHh4eKioqMjIyNjY2Ojo6QkJCRkZGSkpKWlpaXl5ebm5udnZ2enp6goKChoaGkpKSnp6epqamsrKyurq6xsbGzs7O1tbW2tra3t7e4uLi7u7u9vb3BwcHCwsLDw8PGxsbKysrNzc3Ozs7R0dHS0tLT09PZ2dna2trc3Nzd3d3e3t7g4ODh4eHj4+Pk5OTm5ubn5+fo6Ojp6enu7u7w8PDz8/P0Qzb09PT29vb39/f5+fn6+vr7+/v8/Pz9/f3+/v7/wQf///+dc+aLAAAAAWJLR0RPbmZBSQAAAcFJREFUeNrt3ds2AgEYhuHsaSOZbAvZi0r2YYjCJOW7/7txZhkcDNbM6h/vdwfPmlX/ybtmEorJErGCeJLadz3rkKPpZamaLTp925DHdFvSpKelU9uQ/cLKhtcdk7YqtiHruevtojch7ZZtQ0o1dcdfRqXNqm1IbVU3OaVamm/YhvQW5zIXOknnC5JUt7qEpE5fUv/5HVePy2UHAgQIECBAgAABAgQIECBxgrwGHBAgQIAAAQIECBAgQIAAAQIECJC/QRIBN0iQ+66voDMLuRp2fQWdVUhvNun6CjqrkJ0Dx/UVdEYhzXzfcX0FnVFIrlSZ2mx/LOiMQuqHh6k972NBZ/fv13G/KeiCQkIu4358EL8UdBafSGwuOxAgQIAAAQIECJDYQB4CDggQIECAAAECBAgQIECAAAECBAgQIECA/BrSufn0DjqjkEZmLXkWaUEXEmThXMeFSAu68H4j5b1IC7rQILfZTqQFXViQ1nRTL1EWdCFBnmYuJUVZ0IUEWR1xHKcXZUEXFDLwBR2XHQgQIECAAAEC5H9ChgIOCBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgxiBmv+L6Bl9pkxYph15gAAAAAElFTkSuQmCC",
"description": "Displays latest values of the attributes or time-series data for multiple entities as separate bars.",
"descriptor": {
"type": "latest",
"sizeX": 7,
"sizeY": 5,
"resources": [
{
"url": "https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.3.0/Chart.min.js"
}
],
"templateHtml": "<canvas id=\"barChart\"></canvas>\n",
"templateCss": "",
"controllerScript": "self.onInit = function() {\n $scope = self.ctx.$scope;\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showTooltip = utils.defaultValue(settings.showTooltip, true);\n \n Chart.defaults.global.tooltips.enabled = settings.showTooltip;\n \n var barData = {\n labels: [],\n datasets: []\n };\n \n for (var i = 0; i < self.ctx.datasources.length; i++) {\n var datasource = self.ctx.datasources[i];\n for (var d = 0; d < datasource.dataKeys.length; d++) {\n var dataKey = datasource.dataKeys[d];\n var units = dataKey.units && dataKey.units.length ? dataKey.units : self.ctx.units;\n units = units ? (' (' + units + ')') : '';\n var dataset = {\n label: dataKey.label + units,\n data: [0],\n backgroundColor: [dataKey.color],\n borderColor: [dataKey.color],\n borderWidth: 1\n }\n barData.datasets.push(dataset);\n }\n }\n\n var ctx = $('#barChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'bar',\n data: barData,\n options: {\n responsive: false,\n maintainAspectRatio: false,\n scales: {\n yAxes: [{\n ticks: {\n beginAtZero:true\n }\n }]\n }\n }\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n var c = 0;\n for (var i = 0; i < self.ctx.chart.data.datasets.length; i++) {\n var dataset = self.ctx.chart.data.datasets[i];\n var cellData = self.ctx.data[i]; \n if (cellData.data.length > 0) {\n var decimals;\n if (typeof cellData.dataKey.decimals !== 'undefined' \n && cellData.dataKey.decimals !== null ) {\n decimals = cellData.dataKey.decimals; \n } else {\n decimals = self.ctx.decimals;\n }\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = self.ctx.utils.formatValue(tvPair[1], decimals);\n dataset.data[0] = parseFloat(value);\n }\n }\n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n self.ctx.chart.resize();\n}\n\nself.onDestroy = function() {\n self.ctx.chart.destroy();\n self.ctx.chart = null;\n}\n",
"settingsSchema": "",
"dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-chart-widget-settings",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Bars\"}"
},
"externalId": null,
"tags": [
"bar",
"bar chart"
]
}

2
application/src/main/data/json/system/widget_types/doughnut.json

@ -17,7 +17,7 @@
"settingsDirective": "tb-doughnut-widget-settings",
"hasBasicMode": true,
"basicModeDirective": "tb-doughnut-basic-config",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Wind power\",\"color\":\"#08872B\",\"settings\":{},\"_hash\":0.7227918773301678,\"funcBody\":\"if (prevValue === 0) {\\n prevValue = Math.random() * 200;\\n}\\nvar value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 200) {\\n\\tvalue = 200;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Solar power\",\"color\":\"#FF4D5A\",\"settings\":{},\"_hash\":0.2392660816082064,\"funcBody\":\"if (prevValue === 0) {\\n prevValue = Math.random() * 200;\\n}\\nvar value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 200) {\\n\\tvalue = 200;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"layout\":\"default\",\"autoScale\":true,\"totalValueFont\":{\"family\":\"Roboto\",\"size\":24,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"1\"},\"totalValueColor\":{\"type\":\"constant\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"colorFunction\":\"var temperature = value;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\"},\"showLegend\":true,\"legendPosition\":\"bottom\",\"legendLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"legendLabelColor\":\"rgba(0, 0, 0, 0.38)\",\"legendValueFont\":{\"family\":\"Roboto\",\"size\":14,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"20px\"},\"legendValueColor\":\"rgba(0, 0, 0, 0.87)\",\"showTooltip\":true,\"tooltipValueType\":\"percentage\",\"tooltipValueDecimals\":0,\"tooltipValueFont\":{\"family\":\"Roboto\",\"size\":13,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"16px\"},\"tooltipValueColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipBackgroundColor\":\"rgba(255, 255, 255, 0.76)\",\"tooltipBackgroundBlur\":4,\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}}},\"title\":\"Doughnut\",\"dropShadow\":true,\"enableFullscreen\":false,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":null,\"decimals\":0,\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{\"headerButton\":[]},\"configMode\":\"basic\",\"displayTimewindow\":true,\"margin\":\"0px\",\"borderRadius\":\"0px\",\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showTitleIcon\":false,\"titleTooltip\":\"\",\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleIcon\":\"donut_large\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"timewindowStyle\":{\"showIcon\":true,\"iconSize\":\"14px\",\"icon\":\"query_builder\",\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":null,\"weight\":null,\"style\":null,\"lineHeight\":\"1\"},\"color\":null},\"titleColor\":\"rgba(0, 0, 0, 0.87)\"}"
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Wind power\",\"color\":\"#08872B\",\"settings\":{},\"_hash\":0.7227918773301678,\"funcBody\":\"if (prevValue === 0) {\\n prevValue = Math.random() * 200;\\n}\\nvar value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 200) {\\n\\tvalue = 200;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Solar power\",\"color\":\"#FF4D5A\",\"settings\":{},\"_hash\":0.2392660816082064,\"funcBody\":\"if (prevValue === 0) {\\n prevValue = Math.random() * 200;\\n}\\nvar value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 200) {\\n\\tvalue = 200;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{},\"title\":\"Doughnut\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":null,\"decimals\":0,\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{\"headerButton\":[]},\"configMode\":\"basic\",\"displayTimewindow\":true,\"margin\":\"0px\",\"borderRadius\":\"0px\",\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showTitleIcon\":false,\"titleTooltip\":\"\",\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleIcon\":\"donut_large\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"timewindowStyle\":{\"showIcon\":false,\"iconSize\":\"24px\",\"icon\":null,\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"400\",\"style\":\"normal\",\"lineHeight\":\"16px\"},\"color\":\"rgba(0, 0, 0, 0.38)\",\"displayTypePrefix\":true},\"titleColor\":\"rgba(0, 0, 0, 0.87)\"}"
},
"externalId": null,
"tags": [

2
application/src/main/data/json/system/widget_types/horizontal_doughnut.json

@ -17,7 +17,7 @@
"settingsDirective": "tb-doughnut-widget-settings",
"hasBasicMode": true,
"basicModeDirective": "tb-doughnut-basic-config",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Wind power\",\"color\":\"#08872B\",\"settings\":{},\"_hash\":0.7227918773301678,\"funcBody\":\"if (prevValue === 0) {\\n prevValue = Math.random() * 200;\\n}\\nvar value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 200) {\\n\\tvalue = 200;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Solar power\",\"color\":\"#FF4D5A\",\"settings\":{},\"_hash\":0.2392660816082064,\"funcBody\":\"if (prevValue === 0) {\\n prevValue = Math.random() * 200;\\n}\\nvar value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 200) {\\n\\tvalue = 200;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"layout\":\"default\",\"autoScale\":true,\"totalValueFont\":{\"family\":\"Roboto\",\"size\":24,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"1\"},\"totalValueColor\":{\"type\":\"constant\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"colorFunction\":\"var temperature = value;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\"},\"showLegend\":true,\"legendPosition\":\"right\",\"legendLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"legendLabelColor\":\"rgba(0, 0, 0, 0.38)\",\"legendValueFont\":{\"family\":\"Roboto\",\"size\":14,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"20px\"},\"legendValueColor\":\"rgba(0, 0, 0, 0.87)\",\"showTooltip\":true,\"tooltipValueType\":\"percentage\",\"tooltipValueDecimals\":0,\"tooltipValueFont\":{\"family\":\"Roboto\",\"size\":13,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"16px\"},\"tooltipValueColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipBackgroundColor\":\"rgba(255, 255, 255, 0.76)\",\"tooltipBackgroundBlur\":4,\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}}},\"title\":\"Doughnut\",\"dropShadow\":true,\"enableFullscreen\":false,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":null,\"decimals\":0,\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{\"headerButton\":[]},\"configMode\":\"basic\",\"displayTimewindow\":true,\"margin\":\"0px\",\"borderRadius\":\"0px\",\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showTitleIcon\":false,\"titleTooltip\":\"\",\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleIcon\":\"donut_large\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"timewindowStyle\":{\"showIcon\":true,\"iconSize\":\"14px\",\"icon\":\"query_builder\",\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":null,\"weight\":null,\"style\":null,\"lineHeight\":\"1\"},\"color\":null},\"titleColor\":\"rgba(0, 0, 0, 0.87)\"}"
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Wind power\",\"color\":\"#08872B\",\"settings\":{},\"_hash\":0.7227918773301678,\"funcBody\":\"if (prevValue === 0) {\\n prevValue = Math.random() * 200;\\n}\\nvar value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 200) {\\n\\tvalue = 200;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Solar power\",\"color\":\"#FF4D5A\",\"settings\":{},\"_hash\":0.2392660816082064,\"funcBody\":\"if (prevValue === 0) {\\n prevValue = Math.random() * 200;\\n}\\nvar value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 200) {\\n\\tvalue = 200;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{},\"title\":\"Doughnut\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":null,\"decimals\":0,\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{\"headerButton\":[]},\"configMode\":\"basic\",\"displayTimewindow\":true,\"margin\":\"0px\",\"borderRadius\":\"0px\",\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showTitleIcon\":false,\"titleTooltip\":\"\",\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleIcon\":\"donut_large\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"timewindowStyle\":{\"showIcon\":false,\"iconSize\":\"24px\",\"icon\":null,\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"400\",\"style\":\"normal\",\"lineHeight\":\"16px\"},\"color\":\"rgba(0, 0, 0, 0.38)\",\"displayTypePrefix\":true},\"titleColor\":\"rgba(0, 0, 0, 0.87)\"}"
},
"externalId": null,
"tags": [

26
application/src/main/data/json/system/widget_types/pie.json

@ -0,0 +1,26 @@
{
"fqn": "pie",
"name": "Pie",
"deprecated": false,
"image": "tb-image:cGllX2NoYXJ0LnN2Zw==:IlBpZSIgc3lzdGVtIHdpZGdldCBpbWFnZQ==;data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAxIiBoZWlnaHQ9IjE2MCIgdmlld0JveD0iMCAwIDIwMSAxNjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIGZpbHRlcj0idXJsKCNmaWx0ZXIwX2RfNDUzOF8zNTMwNikiPgo8cmVjdCB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE2MCIgcng9IjIuMzg4MDYiIGZpbGw9IndoaXRlIiBzaGFwZS1yZW5kZXJpbmc9ImNyaXNwRWRnZXMiLz4KPHBhdGggZD0iTTE0OC45NzUgMTI5LjQ5N0MxNTYuNzc0IDEyMS42OTggMTYyLjYyIDExMi4xNjYgMTY2LjAzNiAxMDEuNjc4TDk5LjQ3NzUgODBMMTQ4Ljk3NSAxMjkuNDk3WiIgZmlsbD0iI0ZGREUzMCIgc3Ryb2tlPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNOTkuNDc3NSAxMEM4Ny45NjYgMTAgNzYuNjMyMSAxMi44MzkgNjYuNDc5OCAxOC4yNjU1QzU2LjMyNzUgMjMuNjkyIDQ3LjY3MDIgMzEuNTM4NiA0MS4yNzQ3IDQxLjExMDFDMzQuODc5MiA1MC42ODE2IDMwLjk0MjkgNjEuNjgyNyAyOS44MTQ2IDczLjEzODhDMjguNjg2MyA4NC41OTQ5IDMwLjQwMDcgOTYuMTUyNSAzNC44MDYgMTA2Ljc4OEMzOS4yMTEzIDExNy40MjMgNDYuMTcxNCAxMjYuODA4IDU1LjA3IDEzNC4xMTFDNjMuOTY4NiAxNDEuNDE0IDc0LjUzMDggMTQ2LjQwOSA4NS44MjEyIDE0OC42NTVDOTcuMTExNiAxNTAuOTAxIDEwOC43ODIgMTUwLjMyNyAxMTkuNzk3IDE0Ni45ODZDMTMwLjgxMyAxNDMuNjQ0IDE0MC44MzUgMTM3LjYzNyAxNDguOTc1IDEyOS40OTdMOTkuNDc3NSA4MFYxMFoiIGZpbGw9IiMwODg3MkIiIHN0cm9rZT0id2hpdGUiLz4KPGcgZmlsdGVyPSJ1cmwoI2ZpbHRlcjFfZF80NTM4XzM1MzA2KSI+CjxwYXRoIGQ9Ik0xNjcuNTYyIDEwMi4wNTdDMTcxLjAxNCA5MS4yNTc1IDE3MS44ODIgNzkuNzg5OCAxNzAuMDk2IDY4LjU4ODVDMTY4LjMxMSA1Ny4zODcxIDE2My45MjIgNDYuNzY5MiAxNTcuMjg4IDM3LjYwMDJDMTUwLjY1MyAyOC40MzEyIDE0MS45NjEgMjAuOTcwNiAxMzEuOTIgMTUuODI2NkMxMjEuODc5IDEwLjY4MjYgMTEwLjc3MiA4LjAwMDc4IDk5LjUwNjQgNy45OTk3Nkw5OS41IDc5Ljk5OThMMTY3LjU2MiAxMDIuMDU3WiIgZmlsbD0iI0ZGNEQ1QSIvPgo8cGF0aCBkPSJNMTY3LjU2MiAxMDIuMDU3QzE3MS4wMTQgOTEuMjU3NSAxNzEuODgyIDc5Ljc4OTggMTcwLjA5NiA2OC41ODg1QzE2OC4zMTEgNTcuMzg3MSAxNjMuOTIyIDQ2Ljc2OTIgMTU3LjI4OCAzNy42MDAyQzE1MC42NTMgMjguNDMxMiAxNDEuOTYxIDIwLjk3MDYgMTMxLjkyIDE1LjgyNjZDMTIxLjg3OSAxMC42ODI2IDExMC43NzIgOC4wMDA3OCA5OS41MDY0IDcuOTk5NzZMOTkuNSA3OS45OTk4TDE2Ny41NjIgMTAyLjA1N1oiIHN0cm9rZT0id2hpdGUiLz4KPC9nPgo8L2c+CjxkZWZzPgo8ZmlsdGVyIGlkPSJmaWx0ZXIwX2RfNDUzOF8zNTMwNiIgeD0iLTcuMTY0MTgiIHk9Ii00Ljc3NjEyIiB3aWR0aD0iMjE0LjMyOCIgaGVpZ2h0PSIxNzQuMzI4IiBmaWx0ZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+CjxmZUZsb29kIGZsb29kLW9wYWNpdHk9IjAiIHJlc3VsdD0iQmFja2dyb3VuZEltYWdlRml4Ii8+CjxmZUNvbG9yTWF0cml4IGluPSJTb3VyY2VBbHBoYSIgdHlwZT0ibWF0cml4IiB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDEyNyAwIiByZXN1bHQ9ImhhcmRBbHBoYSIvPgo8ZmVPZmZzZXQgZHk9IjIuMzg4MDYiLz4KPGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iMy41ODIwOSIvPgo8ZmVDb21wb3NpdGUgaW4yPSJoYXJkQWxwaGEiIG9wZXJhdG9yPSJvdXQiLz4KPGZlQ29sb3JNYXRyaXggdHlwZT0ibWF0cml4IiB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAuMDYgMCIvPgo8ZmVCbGVuZCBtb2RlPSJub3JtYWwiIGluMj0iQmFja2dyb3VuZEltYWdlRml4IiByZXN1bHQ9ImVmZmVjdDFfZHJvcFNoYWRvd180NTM4XzM1MzA2Ii8+CjxmZUJsZW5kIG1vZGU9Im5vcm1hbCIgaW49IlNvdXJjZUdyYXBoaWMiIGluMj0iZWZmZWN0MV9kcm9wU2hhZG93XzQ1MzhfMzUzMDYiIHJlc3VsdD0ic2hhcGUiLz4KPC9maWx0ZXI+CjxmaWx0ZXIgaWQ9ImZpbHRlcjFfZF80NTM4XzM1MzA2IiB4PSI4NyIgeT0iLTQuNTAwMjQiIHdpZHRoPSI5Ni41IiBoZWlnaHQ9IjExOS4xODciIGZpbHRlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KPGZlRmxvb2QgZmxvb2Qtb3BhY2l0eT0iMCIgcmVzdWx0PSJCYWNrZ3JvdW5kSW1hZ2VGaXgiLz4KPGZlQ29sb3JNYXRyaXggaW49IlNvdXJjZUFscGhhIiB0eXBlPSJtYXRyaXgiIHZhbHVlcz0iMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMTI3IDAiIHJlc3VsdD0iaGFyZEFscGhhIi8+CjxmZU9mZnNldC8+CjxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjYiLz4KPGZlQ29tcG9zaXRlIGluMj0iaGFyZEFscGhhIiBvcGVyYXRvcj0ib3V0Ii8+CjxmZUNvbG9yTWF0cml4IHR5cGU9Im1hdHJpeCIgdmFsdWVzPSIwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwLjI1IDAiLz4KPGZlQmxlbmQgbW9kZT0ibm9ybWFsIiBpbjI9IkJhY2tncm91bmRJbWFnZUZpeCIgcmVzdWx0PSJlZmZlY3QxX2Ryb3BTaGFkb3dfNDUzOF8zNTMwNiIvPgo8ZmVCbGVuZCBtb2RlPSJub3JtYWwiIGluPSJTb3VyY2VHcmFwaGljIiBpbjI9ImVmZmVjdDFfZHJvcFNoYWRvd180NTM4XzM1MzA2IiByZXN1bHQ9InNoYXBlIi8+CjwvZmlsdGVyPgo8L2RlZnM+Cjwvc3ZnPgo=",
"description": "Displays the latest values of the attributes or time-series data in a pie chart. Supports numeric values only.",
"descriptor": {
"type": "latest",
"sizeX": 5,
"sizeY": 4,
"resources": [],
"templateHtml": "<tb-pie-chart-widget \n [ctx]=\"ctx\"\n [widgetTitlePanel]=\"widgetTitlePanel\">\n</tb-pie-chart-widget>",
"templateCss": "",
"controllerScript": "self.onInit = function() {\n self.ctx.$scope.pieChartWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.pieChartWidget.onDataUpdated();\n};\n\nself.typeParameters = function() {\n return {\n previewWidth: '500px',\n previewHeight: '380px',\n embedTitlePanel: true,\n defaultDataKeysFunction: function() {\n return [{ name: 'windPower', label: 'Wind', type: 'timeseries', color: '#08872B' },\n { name: 'solarPower', label: 'Solar', type: 'timeseries', color: '#FF4D5A' },\n { name: 'hydroelectricPower', label: 'Hydroelectric', type: 'timeseries', color: '#FFDE30' }];\n }\n };\n};\n\nself.onDestroy = function() {\n};\n\nself.actionSources = function() {\n return {\n 'sliceClick': {\n name: 'widget-action.pie-slice-click',\n multiple: false\n }\n };\n}\n",
"settingsSchema": "",
"dataKeySettingsSchema": "",
"settingsDirective": "tb-pie-chart-widget-settings",
"hasBasicMode": true,
"basicModeDirective": "tb-pie-chart-basic-config",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Wind\",\"color\":\"#08872B\",\"settings\":{},\"_hash\":0.7227918773301678,\"funcBody\":\"if (prevValue === 0) {\\n prevValue = Math.random() * 200;\\n}\\nvar value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 200) {\\n\\tvalue = 200;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Solar\",\"color\":\"#FF4D5A\",\"settings\":{},\"_hash\":0.2392660816082064,\"funcBody\":\"if (prevValue === 0) {\\n prevValue = Math.random() * 200;\\n}\\nvar value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 200) {\\n\\tvalue = 200;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Hydroelectric\",\"color\":\"#FFDE30\",\"settings\":{},\"_hash\":0.7051898468567794,\"funcBody\":\"if (prevValue === 0) {\\n prevValue = Math.random() * 200;\\n}\\nvar value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 200) {\\n\\tvalue = 200;\\n}\\nreturn value;\",\"decimals\":0,\"aggregationType\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":null,\"padding\":\"0px\",\"settings\":{},\"title\":\"Pie\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":null,\"decimals\":0,\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{\"headerButton\":[]},\"configMode\":\"basic\",\"displayTimewindow\":true,\"margin\":\"0px\",\"borderRadius\":\"\",\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showTitleIcon\":false,\"titleTooltip\":\"\",\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleIcon\":\"pie_chart\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"timewindowStyle\":{\"showIcon\":false,\"iconSize\":\"24px\",\"icon\":null,\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"400\",\"style\":\"normal\",\"lineHeight\":\"16px\"},\"color\":\"rgba(0, 0, 0, 0.38)\",\"displayTypePrefix\":true}}"
},
"tags": [
"pie chart",
"pie"
]
}

6
application/src/main/data/json/system/widget_types/pie___chart_js.json

File diff suppressed because one or more lines are too long

6
application/src/main/data/json/system/widget_types/pie___flot.json

File diff suppressed because one or more lines are too long

37
application/src/main/data/json/system/widget_types/polar_area.json

File diff suppressed because one or more lines are too long

25
application/src/main/data/json/system/widget_types/polar_area_deprecated.json

File diff suppressed because one or more lines are too long

33
application/src/main/data/json/system/widget_types/radar.json

File diff suppressed because one or more lines are too long

25
application/src/main/data/json/system/widget_types/radar_deprecated.json

File diff suppressed because one or more lines are too long

26
application/src/main/data/json/system/widget_types/status_widget.json

File diff suppressed because one or more lines are too long

25
application/src/main/java/org/apache/kafka/common/network/NetworkReceive.java

@ -38,10 +38,10 @@ import java.util.stream.Collectors;
*/
public class NetworkReceive implements Receive {
public final static String UNKNOWN_SOURCE = "";
public final static int UNLIMITED = -1;
public final static int TB_MAX_REQUESTED_BUFFER_SIZE = 100 * 1024 * 1024;
public final static int TB_LOG_REQUESTED_BUFFER_SIZE = 10 * 1024 * 1024;
public static final String UNKNOWN_SOURCE = "";
public static final int UNLIMITED = -1;
public static final int TB_MAX_REQUESTED_BUFFER_SIZE = 100 * 1024 * 1024;
public static final int TB_LOG_REQUESTED_BUFFER_SIZE = 10 * 1024 * 1024;
private static final Logger log = LoggerFactory.getLogger(NetworkReceive.class);
private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0);
@ -54,27 +54,16 @@ public class NetworkReceive implements Receive {
public NetworkReceive(String source, ByteBuffer buffer) {
this.source = source;
this(TB_MAX_REQUESTED_BUFFER_SIZE, source);
this.buffer = buffer;
this.size = null;
this.maxSize = TB_MAX_REQUESTED_BUFFER_SIZE;
this.memoryPool = MemoryPool.NONE;
}
public NetworkReceive(String source) {
this.source = source;
this.size = ByteBuffer.allocate(4);
this.buffer = null;
this.maxSize = TB_MAX_REQUESTED_BUFFER_SIZE;
this.memoryPool = MemoryPool.NONE;
this(TB_MAX_REQUESTED_BUFFER_SIZE, source);
}
public NetworkReceive(int maxSize, String source) {
this.source = source;
this.size = ByteBuffer.allocate(4);
this.buffer = null;
this.maxSize = getMaxSize(maxSize);
this.memoryPool = MemoryPool.NONE;
this(maxSize, source, MemoryPool.NONE);
}
public NetworkReceive(int maxSize, String source, MemoryPool memoryPool) {

2
application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java

@ -23,7 +23,7 @@ import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import jakarta.annotation.Nullable;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.LinkedHashMapRemoveEldest;
import org.thingsboard.server.actors.ActorSystemContext;

6
application/src/main/java/org/thingsboard/server/config/TbRuleEngineSecurityConfiguration.java

@ -40,9 +40,9 @@ public class TbRuleEngineSecurityConfiguration {
.frameOptions(config -> {}).disable())
.cors(cors -> {})
.csrf(AbstractHttpConfigurer::disable)
.authorizeRequests()
.requestMatchers("/actuator/prometheus").permitAll()
.anyRequest().authenticated();
.authorizeHttpRequests(config -> config
.requestMatchers("/actuator/prometheus").permitAll()
.anyRequest().authenticated());
return http.build();
}
}

9
application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java

@ -209,7 +209,7 @@ public class ThingsboardSecurityConfiguration {
.csrf(AbstractHttpConfigurer::disable)
.exceptionHandling(config -> {})
.sessionManagement(config -> config.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeRequests(config -> config
.authorizeHttpRequests(config -> config
.requestMatchers(WEBJARS_ENTRY_POINT).permitAll() // Webjars
.requestMatchers(DEVICE_API_ENTRY_POINT).permitAll() // Device HTTP Transport API
.requestMatchers(FORM_BASED_LOGIN_ENTRY_POINT).permitAll() // Login end-point
@ -217,10 +217,11 @@ public class ThingsboardSecurityConfiguration {
.requestMatchers(TOKEN_REFRESH_ENTRY_POINT).permitAll() // Token refresh end-point
.requestMatchers(MAIL_OAUTH2_PROCESSING_ENTRY_POINT).permitAll() // Mail oauth2 code processing url
.requestMatchers(DEVICE_CONNECTIVITY_CERTIFICATE_DOWNLOAD_ENTRY_POINT).permitAll() // Device connectivity certificate (public)
.requestMatchers(NON_TOKEN_BASED_AUTH_ENTRY_POINTS).permitAll()) // static resources, user activation and password reset end-points
.authorizeRequests(config -> config
.requestMatchers(NON_TOKEN_BASED_AUTH_ENTRY_POINTS).permitAll() // static resources, user activation and password reset end-points
.requestMatchers(WS_ENTRY_POINT).permitAll() // Protected WebSocket API End-points
.requestMatchers(TOKEN_BASED_AUTH_ENTRY_POINT).authenticated()) // Protected API End-points
.formLogin(form -> form
.loginPage("/login").permitAll())
.exceptionHandling(config -> config.accessDeniedHandler(restAccessDeniedHandler))
.addFilterBefore(buildRestLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(buildRestPublicLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
@ -243,7 +244,7 @@ public class ThingsboardSecurityConfiguration {
@Bean
@ConditionalOnMissingBean(CorsFilter.class)
public CorsFilter corsFilter(@Autowired MvcCorsProperties mvcCorsProperties) {
if (mvcCorsProperties.getMappings().size() == 0) {
if (mvcCorsProperties.getMappings().isEmpty()) {
return new CorsFilter(new UrlBasedCorsConfigurationSource());
} else {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

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

@ -19,7 +19,7 @@ import io.swagger.v3.oas.annotations.Parameter;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;

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

@ -21,14 +21,11 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
@ -48,7 +45,6 @@ import org.thingsboard.server.common.data.EventInfo;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
@ -56,7 +52,6 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageDataIterableByTenant;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.data.rule.DefaultRuleChainCreateRequest;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainData;

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

@ -647,7 +647,7 @@ public class TelemetryController extends BaseController {
Map<Long, List<KvEntry>> telemetryRequest;
JsonElement telemetryJson;
try {
telemetryJson = new JsonParser().parse(requestBody);
telemetryJson = JsonParser.parseString(requestBody);
} catch (Exception e) {
return getImmediateDeferredResult("Unable to parse timeseries payload: Invalid JSON body!", HttpStatus.BAD_REQUEST);
}

5
application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java

@ -110,7 +110,7 @@ public class ThingsboardInstallService {
log.info("Upgrading ThingsBoard from version 3.5.1 to 3.6.0 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.5.1");
dataUpdateService.updateData("3.5.1");
systemDataLoaderService.updateDefaultNotificationConfigs();
systemDataLoaderService.updateDefaultNotificationConfigs(true);
case "3.6.0":
log.info("Upgrading ThingsBoard from version 3.6.0 to 3.6.1 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.6.0");
@ -126,13 +126,14 @@ public class ThingsboardInstallService {
case "3.6.2":
log.info("Upgrading ThingsBoard from version 3.6.2 to 3.6.3 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.6.2");
systemDataLoaderService.updateDefaultNotificationConfigs();
systemDataLoaderService.updateDefaultNotificationConfigs(true);
case "3.6.3":
log.info("Upgrading ThingsBoard from version 3.6.3 to 3.6.4 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.6.3");
case "3.6.4":
log.info("Upgrading ThingsBoard from version 3.6.4 to 3.7.0 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.6.4");
systemDataLoaderService.updateDefaultNotificationConfigs(false);
//TODO DON'T FORGET to update switch statement in the CacheCleanupService if you need to clear the cache
break;
default:

2
application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java

@ -20,7 +20,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device;

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

@ -33,6 +33,7 @@ import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.oauth2.OAuth2Info;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
@ -44,6 +45,7 @@ import org.thingsboard.server.dao.eventsourcing.ActionEntityEvent;
import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent;
import org.thingsboard.server.dao.eventsourcing.RelationActionEvent;
import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.user.UserServiceImpl;
import javax.annotation.PostConstruct;
@ -69,6 +71,7 @@ public class EdgeEventSourcingListener {
private final TbClusterService tbClusterService;
private final EdgeSynchronizationManager edgeSynchronizationManager;
private final TenantService tenantService;
@PostConstruct
public void init() {
@ -95,19 +98,24 @@ public class EdgeEventSourcingListener {
@TransactionalEventListener(fallbackExecution = true)
public void handleEvent(DeleteEntityEvent<?> event) {
TenantId tenantId = event.getTenantId();
EntityType entityType = event.getEntityId().getEntityType();
if (!tenantId.isSysTenantId() && !tenantService.tenantExists(tenantId)) {
log.debug("[{}] Ignoring DeleteEntityEvent because tenant does not exist: {}", tenantId, event);
return;
}
try {
if (EntityType.EDGE.equals(entityType) || EntityType.TENANT.equals(entityType)) {
return;
}
log.trace("[{}] DeleteEntityEvent called: {}", event.getTenantId(), event);
log.trace("[{}] DeleteEntityEvent called: {}", tenantId, event);
EdgeEventType type = getEdgeEventTypeForEntityEvent(event.getEntity());
EdgeEventActionType actionType = getEdgeEventActionTypeForEntityEvent(event.getEntity());
tbClusterService.sendNotificationMsgToEdge(event.getTenantId(), null, event.getEntityId(),
tbClusterService.sendNotificationMsgToEdge(tenantId, null, event.getEntityId(),
JacksonUtil.toString(event.getEntity()), type, actionType,
edgeSynchronizationManager.getEdgeId().get());
} catch (Exception e) {
log.error("[{}] failed to process DeleteEntityEvent: {}", event.getTenantId(), event, e);
log.error("[{}] failed to process DeleteEntityEvent: {}", tenantId, event, e);
}
}

11
application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java

@ -27,12 +27,8 @@ import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.edge.EdgeService;
@ -71,13 +67,6 @@ public abstract class AbstractTbEntityService {
@Lazy
private EntitiesVersionControlService vcService;
protected void removeAlarmsByOriginatorId(TenantId tenantId, EntityId entityId) {
PageData<AlarmId> alarms =
alarmService.findAlarmIdsByOriginatorId(tenantId, entityId, new TimePageLink(Integer.MAX_VALUE));
alarms.getData().forEach(alarmId -> alarmService.delAlarm(tenantId, alarmId));
}
protected <T> T checkNotNull(T reference) throws ThingsboardException {
return checkNotNull(reference, "Requested item wasn't found!");
}

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

@ -51,6 +51,7 @@ import org.thingsboard.server.common.msg.rule.engine.DeviceCredentialsUpdateNoti
import org.thingsboard.server.dao.eventsourcing.ActionEntityEvent;
import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent;
import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent;
import org.thingsboard.server.dao.tenant.TenantService;
import javax.annotation.PostConstruct;
import java.util.Set;
@ -61,6 +62,7 @@ import java.util.Set;
public class EntityStateSourcingListener {
private final TbClusterService tbClusterService;
private final TenantService tenantService;
@PostConstruct
public void init() {
@ -71,6 +73,9 @@ public class EntityStateSourcingListener {
public void handleEvent(SaveEntityEvent<?> event) {
TenantId tenantId = event.getTenantId();
EntityId entityId = event.getEntityId();
if (entityId == null) {
return;
}
EntityType entityType = entityId.getEntityType();
log.debug("[{}][{}][{}] Handling entity save event: {}", tenantId, entityType, entityId, event);
boolean isCreated = event.getCreated() != null && event.getCreated();
@ -120,7 +125,14 @@ public class EntityStateSourcingListener {
public void handleEvent(DeleteEntityEvent<?> event) {
TenantId tenantId = event.getTenantId();
EntityId entityId = event.getEntityId();
if (entityId == null) {
return;
}
EntityType entityType = entityId.getEntityType();
if (!tenantId.isSysTenantId() && entityType != EntityType.TENANT && !tenantService.tenantExists(tenantId)) {
log.debug("[{}] Ignoring DeleteEntityEvent because tenant does not exist: {}", tenantId, event);
return;
}
log.debug("[{}][{}][{}] Handling entity deletion event: {}", tenantId, entityType, entityId, event);
switch (entityType) {

16
application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java

@ -176,16 +176,16 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb
}
@Override
public List<AlarmId> unassignDeletedUserAlarms(TenantId tenantId, User user, long unassignTs) {
public List<AlarmId> unassignDeletedUserAlarms(TenantId tenantId, UserId userId, String userTitle, long unassignTs) {
List<AlarmId> totalAlarmIds = new ArrayList<>();
PageLink pageLink = new PageLink(100, 0, null, new SortOrder("id", SortOrder.Direction.ASC));
while (true) {
PageData<AlarmId> pageData = alarmService.findAlarmIdsByAssigneeId(user.getTenantId(), user.getId(), pageLink);
PageData<AlarmId> pageData = alarmService.findAlarmIdsByAssigneeId(tenantId, userId, pageLink);
List<AlarmId> alarmIds = pageData.getData();
if (alarmIds.isEmpty()) {
break;
}
processAlarmsUnassignment(tenantId, user, alarmIds, unassignTs);
processAlarmsUnassignment(tenantId, userId, userTitle, alarmIds, unassignTs);
totalAlarmIds.addAll(alarmIds);
pageLink = pageLink.nextPageLink();
}
@ -204,16 +204,16 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb
return ts > 0 ? ts : System.currentTimeMillis();
}
private void processAlarmsUnassignment(TenantId tenantId, User user, List<AlarmId> alarmIds, long unassignTs) {
private void processAlarmsUnassignment(TenantId tenantId, UserId userId, String userTitle, List<AlarmId> alarmIds, long unassignTs) {
for (AlarmId alarmId : alarmIds) {
log.trace("[{}] Unassigning alarm {} userId {}", tenantId, alarmId, user.getId());
AlarmApiCallResult result = alarmSubscriptionService.unassignAlarm(user.getTenantId(), alarmId, unassignTs);
log.trace("[{}] Unassigning alarm {} userId {}", tenantId, alarmId, userId);
AlarmApiCallResult result = alarmSubscriptionService.unassignAlarm(tenantId, alarmId, unassignTs);
if (!result.isSuccessful()) {
log.error("[{}] Cannot unassign alarm {} userId {}", tenantId, alarmId, user.getId());
log.error("[{}] Cannot unassign alarm {} userId {}", tenantId, alarmId, userId);
continue;
}
if (result.isModified()) {
String comment = String.format("Alarm was unassigned because user %s - was deleted", user.getTitle());
String comment = String.format("Alarm was unassigned because user %s - was deleted", userTitle);
addSystemAlarmComment(result.getAlarm(), null, "ASSIGN", comment);
logEntityActionService.logEntityAction(result.getAlarm().getTenantId(), result.getAlarm().getOriginator(), result.getAlarm(), result.getAlarm().getCustomerId(), ActionType.ALARM_UNASSIGNED, null);
}

2
application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java

@ -41,7 +41,7 @@ public interface TbAlarmService {
AlarmInfo unassign(Alarm alarm, long unassignTs, User user) throws ThingsboardException;
List<AlarmId> unassignDeletedUserAlarms(TenantId tenantId, User user, long unassignTs);
List<AlarmId> unassignDeletedUserAlarms(TenantId tenantId, UserId userId, String userTitle, long unassignTs);
Boolean delete(Alarm alarm, User user);
}

1
application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java

@ -61,7 +61,6 @@ public class DefaultTbAssetService extends AbstractTbEntityService implements Tb
TenantId tenantId = asset.getTenantId();
AssetId assetId = asset.getId();
try {
removeAlarmsByOriginatorId(tenantId, assetId);
assetService.deleteAsset(tenantId, assetId);
logEntityActionService.logEntityAction(tenantId, assetId, asset, asset.getCustomerId(), actionType, user, assetId.toString());
} catch (Exception e) {

1
application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java

@ -94,7 +94,6 @@ public class DefaultTbDeviceService extends AbstractTbEntityService implements T
TenantId tenantId = device.getTenantId();
DeviceId deviceId = device.getId();
try {
removeAlarmsByOriginatorId(tenantId, deviceId);
deviceService.deleteDevice(tenantId, deviceId);
logEntityActionService.logEntityAction(tenantId, deviceId, device, device.getCustomerId(), actionType,
user, deviceId.toString());

118
application/src/main/java/org/thingsboard/server/service/housekeeper/HousekeeperReprocessingService.java

@ -0,0 +1,118 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.housekeeper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.gen.transport.TransportProtos.HousekeeperTaskProto;
import org.thingsboard.server.gen.transport.TransportProtos.ToHousekeeperServiceMsg;
import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.TbQueueProducer;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.housekeeper.HousekeeperConfig;
import org.thingsboard.server.queue.provider.TbCoreQueueFactory;
import org.thingsboard.server.queue.util.AfterStartUp;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.queue.consumer.QueueConsumerManager;
import javax.annotation.PreDestroy;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@TbCoreComponent
@Service
@Slf4j
public class HousekeeperReprocessingService {
private final HousekeeperConfig config;
private final HousekeeperService housekeeperService;
private final QueueConsumerManager<TbProtoQueueMsg<ToHousekeeperServiceMsg>> consumer;
private final TbQueueProducer<TbProtoQueueMsg<ToHousekeeperServiceMsg>> producer;
private final TopicPartitionInfo submitTpi;
private final ExecutorService consumerExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("housekeeper-reprocessing-consumer"));
public HousekeeperReprocessingService(HousekeeperConfig config,
@Lazy HousekeeperService housekeeperService,
TbCoreQueueFactory queueFactory) {
this.config = config;
this.housekeeperService = housekeeperService;
this.consumer = QueueConsumerManager.<TbProtoQueueMsg<ToHousekeeperServiceMsg>>builder()
.name("Housekeeper reprocessing")
.msgPackProcessor(this::processMsgs)
.pollInterval(config.getPollInterval())
.consumerCreator(queueFactory::createHousekeeperReprocessingMsgConsumer)
.consumerExecutor(consumerExecutor)
.build();
this.producer = queueFactory.createHousekeeperReprocessingMsgProducer();
this.submitTpi = TopicPartitionInfo.builder().topic(producer.getDefaultTopic()).build();
}
@AfterStartUp(order = AfterStartUp.REGULAR_SERVICE)
public void afterStartUp() {
consumer.subscribe(); // Kafka topic for tasks reprocessing has only 1 partition, so only one TB Core will reprocess tasks
consumer.launch();
}
private void processMsgs(List<TbProtoQueueMsg<ToHousekeeperServiceMsg>> msgs, TbQueueConsumer<TbProtoQueueMsg<ToHousekeeperServiceMsg>> consumer) throws Exception {
Thread.sleep(config.getTaskReprocessingDelay());
for (TbProtoQueueMsg<ToHousekeeperServiceMsg> msg : msgs) {
log.trace("Reprocessing task: {}", msg);
try {
housekeeperService.processTask(msg.getValue());
} catch (InterruptedException e) {
return;
} catch (Throwable e) {
log.error("Unexpected error during message reprocessing [{}]", msg, e);
submitForReprocessing(msg.getValue(), e);
}
}
consumer.commit();
}
public void submitForReprocessing(ToHousekeeperServiceMsg msg, Throwable error) {
HousekeeperTaskProto task = msg.getTask();
Set<String> errors = new LinkedHashSet<>(task.getErrorsList());
errors.add(StringUtils.truncate(ExceptionUtils.getStackTrace(error), 1024));
msg = msg.toBuilder()
.setTask(task.toBuilder()
.setAttempt(task.getAttempt() + 1)
.clearErrors().addAllErrors(errors)
.build())
.build();
log.trace("Submitting for reprocessing: {}", msg);
producer.send(submitTpi, new TbProtoQueueMsg<>(UUID.randomUUID(), msg), null);
}
@PreDestroy
private void stop() {
consumer.stop();
consumerExecutor.shutdownNow();
log.info("Stopped Housekeeper reprocessing service");
}
}

166
application/src/main/java/org/thingsboard/server/service/housekeeper/HousekeeperService.java

@ -0,0 +1,166 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.housekeeper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.housekeeper.HousekeeperTask;
import org.thingsboard.server.common.data.housekeeper.HousekeeperTaskType;
import org.thingsboard.server.common.data.notification.rule.trigger.TaskProcessingFailureTrigger;
import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor;
import org.thingsboard.server.gen.transport.TransportProtos.ToHousekeeperServiceMsg;
import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.housekeeper.HousekeeperConfig;
import org.thingsboard.server.queue.provider.TbCoreQueueFactory;
import org.thingsboard.server.queue.util.AfterStartUp;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.housekeeper.processor.HousekeeperTaskProcessor;
import org.thingsboard.server.service.housekeeper.stats.HousekeeperStatsService;
import org.thingsboard.server.service.queue.consumer.QueueConsumerManager;
import javax.annotation.PreDestroy;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
@TbCoreComponent
@Service
@Slf4j
public class HousekeeperService {
private final Map<HousekeeperTaskType, HousekeeperTaskProcessor<?>> taskProcessors;
private final HousekeeperConfig config;
private final HousekeeperReprocessingService reprocessingService;
private final Optional<HousekeeperStatsService> statsService;
private final NotificationRuleProcessor notificationRuleProcessor;
private final QueueConsumerManager<TbProtoQueueMsg<ToHousekeeperServiceMsg>> consumer;
private final ExecutorService consumerExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("housekeeper-consumer"));
private final ExecutorService taskExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("housekeeper-task-processor"));
public HousekeeperService(HousekeeperConfig config,
HousekeeperReprocessingService reprocessingService,
TbCoreQueueFactory queueFactory,
Optional<HousekeeperStatsService> statsService,
NotificationRuleProcessor notificationRuleProcessor,
@Lazy List<HousekeeperTaskProcessor<?>> taskProcessors) {
this.config = config;
this.reprocessingService = reprocessingService;
this.statsService = statsService;
this.notificationRuleProcessor = notificationRuleProcessor;
this.consumer = QueueConsumerManager.<TbProtoQueueMsg<ToHousekeeperServiceMsg>>builder()
.name("Housekeeper")
.msgPackProcessor(this::processMsgs)
.pollInterval(config.getPollInterval())
.consumerCreator(queueFactory::createHousekeeperMsgConsumer)
.consumerExecutor(consumerExecutor)
.build();
this.taskProcessors = taskProcessors.stream().collect(Collectors.toMap(HousekeeperTaskProcessor::getTaskType, p -> p));
}
@AfterStartUp(order = AfterStartUp.REGULAR_SERVICE)
public void afterStartUp() {
consumer.subscribe();
consumer.launch();
}
private void processMsgs(List<TbProtoQueueMsg<ToHousekeeperServiceMsg>> msgs, TbQueueConsumer<TbProtoQueueMsg<ToHousekeeperServiceMsg>> consumer) {
for (TbProtoQueueMsg<ToHousekeeperServiceMsg> msg : msgs) {
log.trace("Processing task: {}", msg);
try {
processTask(msg.getValue());
} catch (InterruptedException e) {
return;
} catch (Throwable e) {
log.error("Unexpected error during message processing [{}]", msg, e);
reprocessingService.submitForReprocessing(msg.getValue(), e);
}
}
consumer.commit();
}
@SuppressWarnings("unchecked")
protected <T extends HousekeeperTask> void processTask(ToHousekeeperServiceMsg msg) throws Exception {
HousekeeperTask task = JacksonUtil.fromString(msg.getTask().getValue(), HousekeeperTask.class);
HousekeeperTaskType taskType = task.getTaskType();
if (config.getDisabledTaskTypes().contains(taskType)) {
log.debug("Task type {} is disabled, ignoring {}", taskType, task);
return;
}
HousekeeperTaskProcessor<T> taskProcessor = (HousekeeperTaskProcessor<T>) taskProcessors.get(taskType);
if (taskProcessor == null) {
throw new IllegalArgumentException("Unsupported task type " + taskType);
}
try {
long startTs = System.currentTimeMillis();
Future<Object> future = taskExecutor.submit(() -> {
taskProcessor.process((T) task);
return null;
});
future.get(config.getTaskProcessingTimeout(), TimeUnit.MILLISECONDS);
long timing = System.currentTimeMillis() - startTs;
if (log.isDebugEnabled()) {
log.debug("[{}] Processed {} in {} ms (attempt {})", task.getTenantId(), task.getDescription(), timing, msg.getTask().getAttempt());
}
statsService.ifPresent(statsService -> statsService.reportProcessed(taskType, msg, timing));
} catch (InterruptedException e) {
throw e;
} catch (Throwable e) {
Throwable error = e;
if (e instanceof ExecutionException) {
error = e.getCause();
} else if (e instanceof TimeoutException) {
error = new TimeoutException("Timeout after " + config.getTaskProcessingTimeout() + " seconds");
}
if (msg.getTask().getAttempt() < config.getMaxReprocessingAttempts()) {
log.warn("[{}] Failed to process {} (attempt {}), submitting for reprocessing",
task.getTenantId(), task.getDescription(), msg.getTask().getAttempt(), error);
reprocessingService.submitForReprocessing(msg, error);
} else {
log.error("[{}] Failed to process task in {} attempts: {}", task.getTenantId(), msg.getTask().getAttempt(), msg, e);
notificationRuleProcessor.process(TaskProcessingFailureTrigger.builder()
.task(task)
.error(error)
.attempt(msg.getTask().getAttempt())
.build());
}
statsService.ifPresent(statsService -> statsService.reportFailure(taskType, msg));
}
}
@PreDestroy
private void stop() {
consumer.stop();
consumerExecutor.shutdownNow();
log.info("Stopped Housekeeper service");
}
}

103
application/src/main/java/org/thingsboard/server/service/housekeeper/InMemoryHouseKeeperServiceService.java

@ -1,103 +0,0 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.housekeeper;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.event.TransactionalEventListener;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent;
import org.thingsboard.server.dao.housekeeper.HouseKeeperService;
import org.thingsboard.server.service.entitiy.alarm.TbAlarmService;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
@Component
@RequiredArgsConstructor
@Slf4j
public class InMemoryHouseKeeperServiceService implements HouseKeeperService {
final TbAlarmService alarmService;
ListeningExecutorService executor;
AtomicInteger queueSize = new AtomicInteger();
AtomicInteger totalProcessedCounter = new AtomicInteger();
@PostConstruct
public void init() {
log.debug("Starting HouseKeeper service");
executor = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("housekeeper")));
}
@PreDestroy
public void destroy() {
if (executor != null) {
log.debug("Stopping HouseKeeper service");
executor.shutdown();
}
}
@TransactionalEventListener(fallbackExecution = true)
public void handleEvent(DeleteEntityEvent<?> event) {
log.trace("[{}] DeleteEntityEvent handler: {}", event.getTenantId(), event);
EntityId entityId = event.getEntityId();
if (EntityType.USER.equals(entityId.getEntityType())) {
unassignDeletedUserAlarms(event.getTenantId(), (User) event.getEntity(), event.getTs());
}
}
@Override
public ListenableFuture<List<AlarmId>> unassignDeletedUserAlarms(TenantId tenantId, User user, long unassignTs) {
log.debug("[{}][{}] unassignDeletedUserAlarms submitting, pending queue size: {} ", tenantId, user.getId().getId(), queueSize.get());
queueSize.incrementAndGet();
ListenableFuture<List<AlarmId>> future = executor.submit(() -> alarmService.unassignDeletedUserAlarms(tenantId, user, unassignTs));
Futures.addCallback(future, new FutureCallback<>() {
@Override
public void onSuccess(List<AlarmId> alarmIds) {
queueSize.decrementAndGet();
totalProcessedCounter.incrementAndGet();
log.debug("[{}][{}] unassignDeletedUserAlarms finished, pending queue size: {}, total processed count: {} ",
tenantId, user.getId().getId(), queueSize.get(), totalProcessedCounter.get());
}
@Override
public void onFailure(Throwable throwable) {
queueSize.decrementAndGet();
totalProcessedCounter.incrementAndGet();
log.error("[{}][{}] unassignDeletedUserAlarms failed, pending queue size: {}, total processed count: {}",
tenantId, user.getId().getId(), queueSize.get(), totalProcessedCounter.get(), throwable);
}
}, MoreExecutors.directExecutor());
return future;
}
}

80
application/src/main/java/org/thingsboard/server/service/housekeeper/processor/AlarmsDeletionTaskProcessor.java

@ -0,0 +1,80 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.housekeeper.processor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.housekeeper.AlarmsDeletionHousekeeperTask;
import org.thingsboard.server.common.data.housekeeper.HousekeeperTaskType;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.util.TbPair;
import org.thingsboard.server.dao.alarm.AlarmService;
import java.util.List;
import java.util.UUID;
@Component
@RequiredArgsConstructor
@Slf4j
public class AlarmsDeletionTaskProcessor extends HousekeeperTaskProcessor<AlarmsDeletionHousekeeperTask> {
private final AlarmService alarmService;
@Override
public void process(AlarmsDeletionHousekeeperTask task) throws Exception {
EntityId entityId = task.getEntityId();
EntityType entityType = entityId.getEntityType();
TenantId tenantId = task.getTenantId();
if (entityType == EntityType.DEVICE || entityType == EntityType.ASSET) {
if (task.getAlarms() == null) {
AlarmId lastId = null;
long lastCreatedTime = 0;
while (true) {
List<TbPair<UUID, Long>> alarms = alarmService.findAlarmIdsByOriginatorId(tenantId, entityId, lastCreatedTime, lastId, 128);
if (alarms.isEmpty()) {
break;
}
housekeeperClient.submitTask(new AlarmsDeletionHousekeeperTask(tenantId, entityId, alarms.stream().map(TbPair::getFirst).toList()));
TbPair<UUID, Long> last = alarms.get(alarms.size() - 1);
lastId = new AlarmId(last.getFirst());
lastCreatedTime = last.getSecond();
log.debug("[{}][{}][{}] Submitted task for deleting {} alarms", tenantId, entityType, entityId, alarms.size());
}
} else {
for (UUID alarmId : task.getAlarms()) {
alarmService.delAlarm(tenantId, new AlarmId(alarmId));
}
log.debug("[{}][{}][{}] Deleted {} alarms", tenantId, entityType, entityId, task.getAlarms().size());
}
}
int count = alarmService.deleteEntityAlarmRecords(tenantId, entityId);
log.debug("[{}][{}][{}] Deleted {} entity alarms", tenantId, entityType, entityId, count);
}
@Override
public HousekeeperTaskType getTaskType() {
return HousekeeperTaskType.DELETE_ALARMS;
}
}

47
application/src/main/java/org/thingsboard/server/service/housekeeper/processor/AlarmsUnassignTaskProcessor.java

@ -0,0 +1,47 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.housekeeper.processor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.housekeeper.HousekeeperTaskType;
import org.thingsboard.server.common.data.housekeeper.AlarmsUnassignHousekeeperTask;
import org.thingsboard.server.service.entitiy.alarm.TbAlarmService;
import java.util.List;
@Component
@RequiredArgsConstructor
@Slf4j
public class AlarmsUnassignTaskProcessor extends HousekeeperTaskProcessor<AlarmsUnassignHousekeeperTask> {
private final TbAlarmService alarmService;
@Override
public void process(AlarmsUnassignHousekeeperTask task) throws Exception {
List<AlarmId> alarms = alarmService.unassignDeletedUserAlarms(task.getTenantId(), (UserId) task.getEntityId(), task.getUserTitle(), task.getTs());
log.debug("[{}][{}] Unassigned {} alarms", task.getTenantId(), task.getEntityId(), alarms.size());
}
@Override
public HousekeeperTaskType getTaskType() {
return HousekeeperTaskType.UNASSIGN_ALARMS;
}
}

43
application/src/main/java/org/thingsboard/server/service/housekeeper/processor/AttributesDeletionTaskProcessor.java

@ -0,0 +1,43 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.housekeeper.processor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.common.data.housekeeper.HousekeeperTask;
import org.thingsboard.server.common.data.housekeeper.HousekeeperTaskType;
@Component
@RequiredArgsConstructor
@Slf4j
public class AttributesDeletionTaskProcessor extends HousekeeperTaskProcessor<HousekeeperTask> {
private final AttributesService attributesService;
@Override
public void process(HousekeeperTask task) throws Exception {
int deletedCount = attributesService.removeAllByEntityId(task.getTenantId(), task.getEntityId());
log.debug("[{}][{}][{}] Deleted {} attributes", task.getTenantId(), task.getEntityId().getEntityType(), task.getEntityId(), deletedCount);
}
@Override
public HousekeeperTaskType getTaskType() {
return HousekeeperTaskType.DELETE_ATTRIBUTES;
}
}

57
application/src/main/java/org/thingsboard/server/service/housekeeper/processor/EntitiesDeletionTaskProcessor.java

@ -0,0 +1,57 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.housekeeper.processor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.housekeeper.EntitiesDeletionHousekeeperTask;
import org.thingsboard.server.common.data.housekeeper.HousekeeperTaskType;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.entity.EntityDaoService;
import org.thingsboard.server.dao.entity.EntityServiceRegistry;
import java.util.UUID;
@Component
@RequiredArgsConstructor
@Slf4j
public class EntitiesDeletionTaskProcessor extends HousekeeperTaskProcessor<EntitiesDeletionHousekeeperTask> {
private final EntityServiceRegistry entityServiceRegistry;
@Override
public void process(EntitiesDeletionHousekeeperTask task) throws Exception {
EntityType entityType = task.getEntityType();
TenantId tenantId = task.getTenantId();
EntityDaoService entityService = entityServiceRegistry.getServiceByEntityType(entityType);
for (UUID entityUuid : task.getEntities()) {
EntityId entityId = EntityIdFactory.getByTypeAndUuid(entityType, entityUuid);
entityService.deleteEntity(tenantId, entityId, true);
}
log.debug("[{}] Deleted {} {}s", tenantId, task.getEntities().size(), entityType.getNormalName().toLowerCase());
}
@Override
public HousekeeperTaskType getTaskType() {
return HousekeeperTaskType.DELETE_ENTITIES;
}
}

40
application/src/main/java/org/thingsboard/server/service/housekeeper/processor/EventsDeletionTaskProcessor.java

@ -0,0 +1,40 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.housekeeper.processor;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.common.data.housekeeper.HousekeeperTask;
import org.thingsboard.server.common.data.housekeeper.HousekeeperTaskType;
@Component
@RequiredArgsConstructor
public class EventsDeletionTaskProcessor extends HousekeeperTaskProcessor<HousekeeperTask> {
private final EventService eventService;
@Override
public void process(HousekeeperTask task) throws Exception {
eventService.removeEvents(task.getTenantId(), task.getEntityId(), null, 0L, System.currentTimeMillis());
}
@Override
public HousekeeperTaskType getTaskType() {
return HousekeeperTaskType.DELETE_EVENTS;
}
}

32
application/src/main/java/org/thingsboard/server/service/housekeeper/processor/HousekeeperTaskProcessor.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.service.housekeeper.processor;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.server.common.data.housekeeper.HousekeeperTask;
import org.thingsboard.server.common.data.housekeeper.HousekeeperTaskType;
import org.thingsboard.server.common.msg.housekeeper.HousekeeperClient;
public abstract class HousekeeperTaskProcessor<T extends HousekeeperTask> {
@Autowired
protected HousekeeperClient housekeeperClient;
public abstract void process(T task) throws Exception;
public abstract HousekeeperTaskType getTaskType();
}

45
application/src/main/java/org/thingsboard/server/service/housekeeper/processor/LatestTsDeletionTaskProcessor.java

@ -0,0 +1,45 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.housekeeper.processor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.housekeeper.HousekeeperTaskType;
import org.thingsboard.server.common.data.housekeeper.LatestTsDeletionHousekeeperTask;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import java.util.List;
@Component
@RequiredArgsConstructor
@Slf4j
public class LatestTsDeletionTaskProcessor extends HousekeeperTaskProcessor<LatestTsDeletionHousekeeperTask> {
private final TimeseriesService timeseriesService;
@Override
public void process(LatestTsDeletionHousekeeperTask task) throws Exception {
timeseriesService.removeLatest(task.getTenantId(), task.getEntityId(), List.of(task.getKey())).get();
log.debug("[{}][{}][{}] Deleted latest telemetry for key '{}'", task.getTenantId(), task.getEntityId().getEntityType(), task.getEntityId(), task.getKey());
}
@Override
public HousekeeperTaskType getTaskType() {
return HousekeeperTaskType.DELETE_LATEST_TS;
}
}

60
application/src/main/java/org/thingsboard/server/service/housekeeper/processor/TelemetryDeletionTaskProcessor.java

@ -0,0 +1,60 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.housekeeper.processor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.housekeeper.HousekeeperTask;
import org.thingsboard.server.common.data.housekeeper.HousekeeperTaskType;
import org.thingsboard.server.common.data.housekeeper.LatestTsDeletionHousekeeperTask;
import org.thingsboard.server.common.data.housekeeper.TsHistoryDeletionHousekeeperTask;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import java.util.List;
@Component
@RequiredArgsConstructor
@Slf4j
public class TelemetryDeletionTaskProcessor extends HousekeeperTaskProcessor<HousekeeperTask> {
private final TimeseriesService timeseriesService;
@Override
public void process(HousekeeperTask task) throws Exception {
TenantId tenantId = task.getTenantId();
EntityId entityId = task.getEntityId();
List<String> keys = timeseriesService.findAllKeysByEntityIds(tenantId, List.of(entityId));
for (String key : keys) {
var latestTsDeletionTask = new LatestTsDeletionHousekeeperTask(tenantId, entityId, key);
housekeeperClient.submitTask(latestTsDeletionTask);
var tsHistoryDeletionTask = new TsHistoryDeletionHousekeeperTask(tenantId, entityId, key);
housekeeperClient.submitTask(tsHistoryDeletionTask);
}
log.trace("[{}][{}][{}] Submitted latest and ts history deletion tasks for {} keys", tenantId, entityId.getEntityType(), entityId, keys.size());
}
@Override
public HousekeeperTaskType getTaskType() {
return HousekeeperTaskType.DELETE_TELEMETRY;
}
}

63
application/src/main/java/org/thingsboard/server/service/housekeeper/processor/TenantEntitiesDeletionTaskProcessor.java

@ -0,0 +1,63 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.housekeeper.processor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.housekeeper.EntitiesDeletionHousekeeperTask;
import org.thingsboard.server.common.data.housekeeper.HousekeeperTaskType;
import org.thingsboard.server.common.data.housekeeper.TenantEntitiesDeletionHousekeeperTask;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.entity.EntityDaoRegistry;
import java.util.List;
import java.util.UUID;
@Component
@RequiredArgsConstructor
@Slf4j
public class TenantEntitiesDeletionTaskProcessor extends HousekeeperTaskProcessor<TenantEntitiesDeletionHousekeeperTask> {
private final EntityDaoRegistry entityDaoRegistry;
@Override
public void process(TenantEntitiesDeletionHousekeeperTask task) throws Exception {
EntityType entityType = task.getEntityType();
TenantId tenantId = task.getTenantId();
Dao<?> entityDao = entityDaoRegistry.getDao(entityType);
UUID last = null;
while (true) {
List<UUID> entities = entityDao.findIdsByTenantIdAndIdOffset(tenantId, last, 128);
if (entities.isEmpty()) {
break;
}
housekeeperClient.submitTask(new EntitiesDeletionHousekeeperTask(tenantId, entityType, entities));
last = entities.get(entities.size() - 1);
log.debug("[{}] Submitted task for deleting {} {}s", tenantId, entities.size(), entityType.getNormalName().toLowerCase());
}
}
@Override
public HousekeeperTaskType getTaskType() {
return HousekeeperTaskType.DELETE_TENANT_ENTITIES;
}
}

48
application/src/main/java/org/thingsboard/server/service/housekeeper/processor/TsHistoryDeletionTaskProcessor.java

@ -0,0 +1,48 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.housekeeper.processor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.housekeeper.HousekeeperTaskType;
import org.thingsboard.server.common.data.housekeeper.TsHistoryDeletionHousekeeperTask;
import org.thingsboard.server.common.data.kv.BaseDeleteTsKvQuery;
import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import java.util.List;
@Component
@RequiredArgsConstructor
@Slf4j
public class TsHistoryDeletionTaskProcessor extends HousekeeperTaskProcessor<TsHistoryDeletionHousekeeperTask> {
private final TimeseriesService timeseriesService;
@Override
public void process(TsHistoryDeletionHousekeeperTask task) throws Exception {
DeleteTsKvQuery deleteQuery = new BaseDeleteTsKvQuery(task.getKey(), 0, System.currentTimeMillis(), false, false);
timeseriesService.remove(task.getTenantId(), task.getEntityId(), List.of(deleteQuery)).get();
log.debug("[{}][{}][{}] Deleted timeseries history for key '{}'", task.getTenantId(), task.getEntityId().getEntityType(), task.getEntityId(), task.getKey());
}
@Override
public HousekeeperTaskType getTaskType() {
return HousekeeperTaskType.DELETE_TS_HISTORY;
}
}

125
application/src/main/java/org/thingsboard/server/service/housekeeper/stats/HousekeeperStatsService.java

@ -0,0 +1,125 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.housekeeper.stats;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.housekeeper.HousekeeperTaskType;
import org.thingsboard.server.common.stats.DefaultCounter;
import org.thingsboard.server.common.stats.StatsCounter;
import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.common.stats.StatsTimer;
import org.thingsboard.server.common.stats.StatsType;
import org.thingsboard.server.gen.transport.TransportProtos.ToHousekeeperServiceMsg;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
@Service
@Slf4j
@ConditionalOnProperty(name = "queue.core.housekeeper.stats.enabled", havingValue = "true", matchIfMissing = true)
public class HousekeeperStatsService {
private final Map<HousekeeperTaskType, HousekeeperStats> stats = new EnumMap<>(HousekeeperTaskType.class);
public HousekeeperStatsService(StatsFactory statsFactory) {
for (HousekeeperTaskType taskType : HousekeeperTaskType.values()) {
stats.put(taskType, new HousekeeperStats(taskType, statsFactory));
}
}
@Scheduled(initialDelayString = "${queue.core.housekeeper.stats.print-interval-ms:60000}",
fixedDelayString = "${queue.core.housekeeper.stats.print-interval-ms:60000}")
private void reportStats() {
String statsStr = stats.values().stream().map(stats -> {
String countersStr = stats.getCounters().stream()
.filter(counter -> counter.get() > 0)
.map(counter -> counter.getName() + " = [" + counter.get() + "]")
.collect(Collectors.joining(" "));
if (countersStr.isEmpty()) {
return null;
} else {
return stats.getTaskType() + " " + countersStr + " avgProcessingTime [" + stats.getProcessingTimer().getAvg() + " ms]";
}
}).filter(Objects::nonNull).collect(Collectors.joining(", "));
if (!statsStr.isEmpty()) {
stats.values().forEach(HousekeeperStats::reset);
log.info("Housekeeper stats: {}", statsStr);
}
}
public void reportProcessed(HousekeeperTaskType taskType, ToHousekeeperServiceMsg msg, long timing) {
HousekeeperStats stats = this.stats.get(taskType);
if (msg.getTask().getErrorsCount() == 0) {
stats.getProcessedCounter().increment();
} else {
stats.getReprocessedCounter().increment();
}
stats.getProcessingTimer().record(timing);
}
public void reportFailure(HousekeeperTaskType taskType, ToHousekeeperServiceMsg msg) {
HousekeeperStats stats = this.stats.get(taskType);
if (msg.getTask().getErrorsCount() == 0) {
stats.getFailedProcessingCounter().increment();
} else {
stats.getFailedReprocessingCounter().increment();
}
}
@Getter
static class HousekeeperStats {
private final HousekeeperTaskType taskType;
private final List<StatsCounter> counters = new ArrayList<>();
private final StatsCounter processedCounter;
private final StatsCounter failedProcessingCounter;
private final StatsCounter reprocessedCounter;
private final StatsCounter failedReprocessingCounter;
private final StatsTimer processingTimer;
public HousekeeperStats(HousekeeperTaskType taskType, StatsFactory statsFactory) {
this.taskType = taskType;
this.processedCounter = register("processed", statsFactory);
this.failedProcessingCounter = register("failedProcessing", statsFactory);
this.reprocessedCounter = register("reprocessed", statsFactory);
this.failedReprocessingCounter = register("failedReprocessing", statsFactory);
this.processingTimer = statsFactory.createTimer(StatsType.HOUSEKEEPER, "processingTime", "taskType", taskType.name());
}
private StatsCounter register(String statsName, StatsFactory statsFactory) {
StatsCounter counter = statsFactory.createStatsCounter(StatsType.HOUSEKEEPER.getName(), statsName, "taskType", taskType.name());
counters.add(counter);
return counter;
}
public void reset() {
counters.forEach(DefaultCounter::clear);
processingTimer.reset();
}
}
}

37
application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java

@ -549,7 +549,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
} else {
ListenableFuture<List<String>> saveFuture = attributesService.save(TenantId.SYS_TENANT_ID, deviceId, AttributeScope.SERVER_SCOPE,
Collections.singletonList(new BaseAttributeKvEntry(new BooleanDataEntry(key, value)
, System.currentTimeMillis())));
, System.currentTimeMillis())));
addTsCallback(saveFuture, new TelemetrySaveCallback<>(deviceId, key, value));
}
}
@ -693,23 +693,26 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
@Override
@SneakyThrows
public void updateDefaultNotificationConfigs() {
PageDataIterable<TenantId> tenants = new PageDataIterable<>(tenantService::findTenantsIds, 500);
ExecutorService executor = Executors.newFixedThreadPool(Math.max(Runtime.getRuntime().availableProcessors(), 4));
log.info("Updating default edge failure notification configs for all tenants");
AtomicInteger count = new AtomicInteger();
for (TenantId tenantId : tenants) {
executor.submit(() -> {
notificationSettingsService.updateDefaultNotificationConfigs(tenantId);
int n = count.incrementAndGet();
if (n % 500 == 0) {
log.info("{} tenants processed", n);
}
});
}
executor.shutdown();
executor.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
public void updateDefaultNotificationConfigs(boolean updateTenants) {
log.info("Updating notification configs...");
notificationSettingsService.updateDefaultNotificationConfigs(TenantId.SYS_TENANT_ID);
if (updateTenants) {
PageDataIterable<TenantId> tenants = new PageDataIterable<>(tenantService::findTenantsIds, 500);
ExecutorService executor = Executors.newFixedThreadPool(Math.max(Runtime.getRuntime().availableProcessors(), 4));
AtomicInteger count = new AtomicInteger();
for (TenantId tenantId : tenants) {
executor.submit(() -> {
notificationSettingsService.updateDefaultNotificationConfigs(tenantId);
int n = count.incrementAndGet();
if (n % 500 == 0) {
log.info("{} tenants processed", n);
}
});
}
executor.shutdown();
executor.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
}
}
}

2
application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java

@ -35,6 +35,6 @@ public interface SystemDataLoaderService {
void createDefaultNotificationConfigs();
void updateDefaultNotificationConfigs();
void updateDefaultNotificationConfigs(boolean updateTenants);
}

6
application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java

@ -55,7 +55,11 @@ public class DefaultCacheCleanupService implements CacheCleanupService {
clearCacheByName(RESOURCE_INFO_CACHE);
break;
case "3.6.3":
log.info("Clearing cache to upgrade from version 3.6.3 to 3.7.0");
log.info("Clearing cache to upgrade from version 3.6.3 to 3.6.4");
clearAll();
break;
case "3.6.4":
log.info("Clearing cache to upgrade from version 3.6.4 to 3.7.0");
clearAll();
break;
default:

3
application/src/main/java/org/thingsboard/server/service/notification/channels/MobileAppNotificationChannel.java

@ -105,9 +105,10 @@ public class MobileAppNotificationChannel implements NotificationChannel<User, M
firebaseService.sendMessage(ctx.getTenantId(), credentials, token, subject, body, data, unreadCount);
} catch (FirebaseMessagingException e) {
MessagingErrorCode errorCode = e.getMessagingErrorCode();
if (errorCode == MessagingErrorCode.UNREGISTERED || errorCode == MessagingErrorCode.INVALID_ARGUMENT) {
if (errorCode == MessagingErrorCode.UNREGISTERED || errorCode == MessagingErrorCode.INVALID_ARGUMENT || errorCode == MessagingErrorCode.SENDER_ID_MISMATCH) {
validTokens.remove(token);
userService.removeMobileSession(recipient.getTenantId(), token);
log.debug("[{}][{}] Removed invalid FCM token due to {} {} ({})", recipient.getTenantId(), recipient.getId(), errorCode, e.getMessage(), token);
continue;
}
throw new RuntimeException("Failed to send message via FCM: " + e.getMessage(), e);

2
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmAssignmentTriggerProcessor.java

@ -27,7 +27,7 @@ import org.thingsboard.server.common.data.notification.rule.trigger.config.Alarm
import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerType;
import org.thingsboard.server.common.data.notification.rule.trigger.AlarmAssignmentTrigger;
import static org.apache.commons.collections.CollectionUtils.isEmpty;
import static org.apache.commons.collections4.CollectionUtils.isEmpty;
import static org.thingsboard.server.common.data.util.CollectionsUtil.emptyOrContains;
@Service

2
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmCommentTriggerProcessor.java

@ -29,7 +29,7 @@ import org.thingsboard.server.common.data.notification.rule.trigger.config.Notif
import org.thingsboard.server.common.data.notification.rule.trigger.AlarmCommentTrigger;
import org.thingsboard.server.dao.entity.EntityService;
import static org.apache.commons.collections.CollectionUtils.isEmpty;
import static org.apache.commons.collections4.CollectionUtils.isEmpty;
import static org.thingsboard.server.common.data.util.CollectionsUtil.emptyOrContains;
@Service

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

@ -28,7 +28,7 @@ import org.thingsboard.server.common.data.notification.rule.trigger.config.Alarm
import org.thingsboard.server.common.data.notification.rule.trigger.config.AlarmNotificationRuleTriggerConfig.ClearRule;
import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerType;
import static org.apache.commons.collections.CollectionUtils.isNotEmpty;
import static org.apache.commons.collections4.CollectionUtils.isNotEmpty;
import static org.thingsboard.server.common.data.util.CollectionsUtil.emptyOrContains;
@Service

2
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/DeviceActivityTriggerProcessor.java

@ -16,7 +16,7 @@
package org.thingsboard.server.service.notification.rule.trigger;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.id.DeviceId;

2
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EdgeCommunicationFailureTriggerProcessor.java

@ -16,7 +16,7 @@
package org.thingsboard.server.service.notification.rule.trigger;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.notification.info.EdgeCommunicationFailureNotificationInfo;
import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo;

2
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EdgeConnectionTriggerProcessor.java

@ -16,7 +16,7 @@
package org.thingsboard.server.service.notification.rule.trigger;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.notification.info.EdgeConnectionNotificationInfo;
import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo;

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

@ -27,7 +27,7 @@ import org.thingsboard.server.dao.entity.EntityCountService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantService;
import static org.apache.commons.collections.CollectionUtils.isNotEmpty;
import static org.apache.commons.collections4.CollectionUtils.isNotEmpty;
@Service
@RequiredArgsConstructor

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

@ -16,7 +16,7 @@
package org.thingsboard.server.service.notification.rule.trigger;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.stereotype.Service;
@ -47,7 +47,7 @@ public class RuleEngineComponentLifecycleEventTriggerProcessor implements Notifi
return false;
}
}
if (!partitionService.resolve(ServiceType.TB_RULE_ENGINE, trigger.getTenantId(), trigger.getComponentId()).isMyPartition()) {
if (!partitionService.isMyPartition(ServiceType.TB_RULE_ENGINE, trigger.getTenantId(), trigger.getComponentId())) {
return false;
}

53
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/TaskProcessingFailureTriggerProcessor.java

@ -0,0 +1,53 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.notification.rule.trigger;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.housekeeper.HousekeeperTask;
import org.thingsboard.server.common.data.notification.info.TaskProcessingFailureNotificationInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.TaskProcessingFailureTrigger;
import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerType;
import org.thingsboard.server.common.data.notification.rule.trigger.config.TaskProcessingFailureNotificationRuleTriggerConfig;
@Service
public class TaskProcessingFailureTriggerProcessor implements NotificationRuleTriggerProcessor<TaskProcessingFailureTrigger, TaskProcessingFailureNotificationRuleTriggerConfig> {
@Override
public boolean matchesFilter(TaskProcessingFailureTrigger trigger, TaskProcessingFailureNotificationRuleTriggerConfig triggerConfig) {
return true;
}
@Override
public TaskProcessingFailureNotificationInfo constructNotificationInfo(TaskProcessingFailureTrigger trigger) {
HousekeeperTask task = trigger.getTask();
return TaskProcessingFailureNotificationInfo.builder()
.tenantId(task.getTenantId())
.entityId(task.getEntityId())
.taskType(task.getTaskType())
.taskDescription(task.getDescription())
.error(StringUtils.truncate(ExceptionUtils.getStackTrace(trigger.getError()), 1024))
.attempt(trigger.getAttempt())
.build();
}
@Override
public NotificationRuleTriggerType getTriggerType() {
return NotificationRuleTriggerType.TASK_PROCESSING_FAILURE;
}
}

109
application/src/main/java/org/thingsboard/server/service/queue/consumer/QueueConsumerManager.java

@ -0,0 +1,109 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.queue.consumer;
import lombok.Builder;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.TbQueueMsg;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.function.Supplier;
@Slf4j
public class QueueConsumerManager<M extends TbQueueMsg> {
private final String name;
private final MsgPackProcessor<M> msgPackProcessor;
private final long pollInterval;
private final ExecutorService consumerExecutor;
private final String threadPrefix;
@Getter
private final TbQueueConsumer<M> consumer;
private volatile boolean stopped;
@Builder
public QueueConsumerManager(String name, MsgPackProcessor<M> msgPackProcessor,
long pollInterval, Supplier<TbQueueConsumer<M>> consumerCreator,
ExecutorService consumerExecutor, String threadPrefix) {
this.name = name;
this.pollInterval = pollInterval;
this.msgPackProcessor = msgPackProcessor;
this.consumerExecutor = consumerExecutor;
this.threadPrefix = threadPrefix;
this.consumer = consumerCreator.get();
}
public void subscribe() {
consumer.subscribe();
}
public void subscribe(Set<TopicPartitionInfo> partitions) {
consumer.subscribe(partitions);
}
public void launch() {
log.info("[{}] Launching consumer", name);
consumerExecutor.submit(() -> {
if (threadPrefix != null) {
ThingsBoardThreadFactory.addThreadNamePrefix(threadPrefix);
}
try {
consumerLoop(consumer);
} catch (Throwable e) {
log.error("Failure in consumer loop", e);
}
log.info("[{}] Consumer stopped", name);
});
}
private void consumerLoop(TbQueueConsumer<M> consumer) {
while (!stopped && !consumer.isStopped()) {
try {
List<M> msgs = consumer.poll(pollInterval);
if (msgs.isEmpty()) {
continue;
}
msgPackProcessor.process(msgs, consumer);
} catch (Exception e) {
if (!consumer.isStopped()) {
log.warn("Failed to process messages from queue", e);
try {
Thread.sleep(pollInterval);
} catch (InterruptedException interruptedException) {
log.trace("Failed to wait until the server has capacity to handle new requests", interruptedException);
}
}
}
}
}
public void stop() {
log.debug("[{}] Stopping consumer", name);
stopped = true;
consumer.unsubscribe();
}
public interface MsgPackProcessor<M extends TbQueueMsg> {
void process(List<M> msgs, TbQueueConsumer<M> consumer) throws Exception;
}
}

5
application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/DefaultExportableEntitiesService.java

@ -147,10 +147,7 @@ public class DefaultExportableEntitiesService implements ExportableEntitiesServi
public <I extends EntityId> void removeById(TenantId tenantId, I id) {
EntityType entityType = id.getEntityType();
EntityDaoService entityService = entityServiceRegistry.getServiceByEntityType(entityType);
if (entityService == null) {
throw new IllegalArgumentException("Unsupported entity type " + entityType);
}
entityService.deleteEntity(tenantId, id);
entityService.deleteEntity(tenantId, id, false);
}
private <I extends EntityId, E extends ExportableEntity<I>> ExportableEntityDao<I, E> getExportableEntityDao(EntityType entityType) {

2
application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DashboardExportService.java

@ -16,7 +16,7 @@
package org.thingsboard.server.service.sync.ie.exporting.impl;
import com.fasterxml.jackson.databind.JsonNode;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Dashboard;

2
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/NotificationTargetImportService.java

@ -16,7 +16,7 @@
package org.thingsboard.server.service.sync.ie.importing.impl;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;

1
application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java

@ -56,7 +56,6 @@ public class AlarmsCleanUpService {
private final TenantService tenantService;
private final AlarmDao alarmDao;
private final AlarmService alarmService;
private final RelationService relationService;
private final EntityActionService entityActionService;
private final PartitionService partitionService;
private final TbTenantProfileCache tenantProfileCache;

7
application/src/main/java/org/thingsboard/server/service/ttl/AuditLogsCleanUpService.java

@ -50,7 +50,8 @@ public class AuditLogsCleanUpService extends AbstractCleanUpService {
@Scheduled(initialDelayString = "#{T(org.apache.commons.lang3.RandomUtils).nextLong(0, ${sql.ttl.audit_logs.checking_interval_ms})}",
fixedDelayString = "${sql.ttl.audit_logs.checking_interval_ms}")
public void cleanUp() {
long auditLogsExpTime = System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(ttlInSec);
long auditLogsExpTime = getCurrentTimeMillis() - TimeUnit.SECONDS.toMillis(ttlInSec);
log.debug("cleanup {}", auditLogsExpTime);
if (isSystemTenantPartitionMine()) {
auditLogDao.cleanUpAuditLogs(auditLogsExpTime);
} else {
@ -58,4 +59,8 @@ public class AuditLogsCleanUpService extends AbstractCleanUpService {
}
}
public long getCurrentTimeMillis() {
return System.currentTimeMillis();
}
}

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

@ -24,7 +24,7 @@ import com.google.common.util.concurrent.MoreExecutors;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.CloseStatus;

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

@ -193,7 +193,7 @@ ui:
# Help parameters
help:
# Base URL for UI help assets
base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard-ui-help/release-3.6.3}"
base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard-ui-help/release-3.7}"
# Database telemetry parameters
database:
@ -1297,11 +1297,17 @@ edges:
rpc:
# RPC port bind
port: "${EDGES_RPC_PORT:7070}"
# Maximum time of keeping alive client RPC connection. Time in seconds
# Specifies the minimum amount of time that should elapse between keepalive pings sent by the client
# This prevents clients from sending pings too frequently, which can be a nuisance to the server (potentially even a denial-of-service attack vector if abused)
# If a client sends pings more frequently than this interval, the server may terminate the connection.
client_max_keep_alive_time_sec: "${EDGES_RPC_CLIENT_MAX_KEEP_ALIVE_TIME_SEC:1}"
# Maximum time of keep-alive connection. Time in seconds
# Sets the time of inactivity (no read operations on the connection) after which the server will send a keepalive ping to the client.
# This is used to ensure that the connection is still alive and to prevent network intermediaries from dropping connections due to inactivity.
# It's a way for the server to proactively check if the client is still responsive.
keep_alive_time_sec: "${EDGES_RPC_KEEP_ALIVE_TIME_SEC:10}"
# Timeout of keep alive RPC connection. Time in seconds
# Specifies the maximum amount of time the server waits for a response to its keepalive ping.
# If the ping is not acknowledged within this time frame, the server considers the connection dead and may close it.
# This timeout helps detect unresponsive clients.
keep_alive_timeout_sec: "${EDGES_RPC_KEEP_ALIVE_TIMEOUT_SEC:5}"
ssl:
# Enable/disable SSL support
@ -1445,6 +1451,16 @@ queue:
# tb_rule_engine.sq:
# - key: max.poll.records
# value: "${TB_QUEUE_KAFKA_SQ_MAX_POLL_RECORDS:1024}"
tb_housekeeper:
# Consumer properties for Housekeeper tasks topic
- key: max.poll.records
# Amount of records to be returned in a single poll. For Housekeeper tasks topic, we should consume messages (tasks) one by one
value: "${TB_QUEUE_KAFKA_HOUSEKEEPER_MAX_POLL_RECORDS:1}"
tb_housekeeper.reprocessing:
# Consumer properties for Housekeeper reprocessing topic
- key: max.poll.records
# Amount of records to be returned in a single poll. For Housekeeper reprocessing topic, we should consume messages (tasks) one by one
value: "${TB_QUEUE_KAFKA_HOUSEKEEPER_REPROCESSING_MAX_POLL_RECORDS:1}"
other-inline: "${TB_QUEUE_KAFKA_OTHER_PROPERTIES:}" # In this section you can specify custom parameters (semicolon separated) for Kafka consumer/producer/admin # Example "metrics.recording.level:INFO;metrics.sample.window.ms:30000"
other: # DEPRECATED. In this section, you can specify custom parameters for Kafka consumer/producer and expose the env variables to configure outside
# - key: "request.timeout.ms" # refer to https://docs.confluent.io/platform/current/installation/configuration/producer-configs.html#producerconfigs_request.timeout.ms
@ -1466,6 +1482,10 @@ queue:
ota-updates: "${TB_QUEUE_KAFKA_OTA_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:10;min.insync.replicas:1}"
# Kafka properties for Version Control topic
version-control: "${TB_QUEUE_KAFKA_VC_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}"
# Kafka properties for Housekeeper tasks topic
housekeeper: "${TB_QUEUE_KAFKA_HOUSEKEEPER_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:10;min.insync.replicas:1}"
# Kafka properties for Housekeeper reprocessing topic; retention.ms is set to 90 days; partitions is set to 1 since only one reprocessing service is running at a time
housekeeper-reprocessing: "${TB_QUEUE_KAFKA_HOUSEKEEPER_REPROCESSING_TOPIC_PROPERTIES:retention.ms:7776000000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}"
consumer-stats:
# Prints lag between consumer group offset and last messages offset in Kafka topics
enabled: "${TB_QUEUE_KAFKA_CONSUMER_STATS_ENABLED:true}"
@ -1621,6 +1641,29 @@ queue:
enabled: "${TB_QUEUE_CORE_STATS_ENABLED:true}"
# Statistics printing interval for Core microservices
print-interval-ms: "${TB_QUEUE_CORE_STATS_PRINT_INTERVAL_MS:60000}"
housekeeper:
# Topic name for Housekeeper tasks
topic: "${TB_HOUSEKEEPER_TOPIC:tb_housekeeper}"
# Topic name for Housekeeper tasks to be reprocessed
reprocessing-topic: "${TB_HOUSEKEEPER_REPROCESSING_TOPIC:tb_housekeeper.reprocessing}"
# Poll interval for topics related to Housekeeper
poll-interval-ms: "${TB_HOUSEKEEPER_POLL_INTERVAL_MS:500}"
# Timeout in milliseconds for task processing. Tasks that fail to finish on time will be submitted for reprocessing
task-processing-timeout-ms: "${TB_HOUSEKEEPER_TASK_PROCESSING_TIMEOUT_MS:120000}"
# Comma-separated list of task types that shouldn't be processed. Available task types:
# DELETE_ATTRIBUTES, DELETE_TELEMETRY (both DELETE_LATEST_TS and DELETE_TS_HISTORY will be disabled),
# DELETE_LATEST_TS, DELETE_TS_HISTORY, DELETE_EVENTS, DELETE_ALARMS, UNASSIGN_ALARMS
disabled-task-types: "${TB_HOUSEKEEPER_DISABLED_TASK_TYPES:}"
# Delay in milliseconds between tasks reprocessing
task-reprocessing-delay-ms: "${TB_HOUSEKEEPER_TASK_REPROCESSING_DELAY_MS:3000}"
# Maximum amount of task reprocessing attempts. After exceeding, the task will be dropped
max-reprocessing-attempts: "${TB_HOUSEKEEPER_MAX_REPROCESSING_ATTEMPTS:10}"
stats:
# Enable/disable statistics for Housekeeper
enabled: "${TB_HOUSEKEEPER_STATS_ENABLED:true}"
# Statistics printing interval for Housekeeper
print-interval-ms: "${TB_HOUSEKEEPER_STATS_PRINT_INTERVAL_MS:60000}"
vc:
# Default topic name for Kafka, RabbitMQ, etc.
topic: "${TB_QUEUE_VC_TOPIC:tb_version_control}"
@ -1730,3 +1773,7 @@ management:
exposure:
# Expose metrics endpoint (use value 'prometheus' to enable prometheus metrics).
include: '${METRICS_ENDPOINTS_EXPOSE:info}'
health:
elasticsearch:
# Enable the org.springframework.boot.actuate.elasticsearch.ElasticsearchRestClientHealthIndicator.doHealthCheck
enabled: "false"

41
application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java

@ -25,7 +25,6 @@ import io.jsonwebtoken.Header;
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.Jwts;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpStatus;
import org.awaitility.Awaitility;
import org.hamcrest.Matcher;
import org.hibernate.exception.ConstraintViolationException;
@ -114,9 +113,11 @@ import org.thingsboard.server.common.data.tenant.profile.TenantProfileData;
import org.thingsboard.server.common.msg.session.FeatureType;
import org.thingsboard.server.config.ThingsboardSecurityConfiguration;
import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.device.ClaimDevicesService;
import org.thingsboard.server.dao.tenant.TenantProfileService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.queue.memory.InMemoryStorage;
import org.thingsboard.server.service.entitiy.tenant.profile.TbTenantProfileService;
import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRequest;
import org.thingsboard.server.service.security.auth.rest.LoginRequest;
@ -229,7 +230,10 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
private TbTenantProfileService tbTenantProfileService;
@Autowired
public TimeseriesService tsService;
protected TimeseriesService tsService;
@Autowired
protected AttributesService attributesService;
@Autowired
protected DefaultActorService actorService;
@ -240,6 +244,9 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
@SpyBean
protected MailService mailService;
@Autowired
protected InMemoryStorage storage;
@Rule
public TestRule watcher = new TestWatcher() {
protected void starting(Description description) {
@ -282,7 +289,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
Tenant tenant = new Tenant();
tenant.setTitle(TEST_TENANT_NAME);
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Tenant savedTenant = saveTenant(tenant);
Assert.assertNotNull(savedTenant);
tenantId = savedTenant.getId();
tenantProfileId = savedTenant.getTenantProfileId();
@ -360,22 +367,14 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
assertThat(loadedTenants).as("All tenants expected to be deleted, but some tenants left in the database").isEmpty();
}
private void deleteTenant(TenantId tenantId) {
int status = 0;
int retries = 0;
while (status != HttpStatus.SC_OK && retries < CLEANUP_TENANT_RETRIES_COUNT) {
retries++;
try {
status = doDelete("/api/tenant/" + tenantId.getId().toString())
.andReturn().getResponse().getStatus();
if (status != HttpStatus.SC_OK) {
log.warn("Tenant deletion failed, tenantId: {}", tenantId.getId().toString());
Thread.sleep(1000L);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
protected void deleteTenant(TenantId tenantId) {
try {
doDelete("/api/tenant/" + tenantId.getId()).andExpect(status().isOk());
} catch (Exception e) {
throw new RuntimeException(e);
}
Awaitility.await("all tasks processed").atMost(60, TimeUnit.SECONDS).during(300, TimeUnit.MILLISECONDS)
.until(() -> storage.getLag("tb_housekeeper") == 0);
}
private List<Tenant> getAllTenants() throws Exception {
@ -428,7 +427,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle(TEST_DIFFERENT_TENANT_NAME);
savedDifferentTenant = doPost("/api/tenant", tenant, Tenant.class);
savedDifferentTenant = saveTenant(tenant);
differentTenantId = savedDifferentTenant.getId();
Assert.assertNotNull(savedDifferentTenant);
User differentTenantAdmin = new User();
@ -438,6 +437,10 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
savedDifferentTenantUser = createUserAndLogin(differentTenantAdmin, DIFFERENT_TENANT_ADMIN_PASSWORD);
}
protected Tenant saveTenant(Tenant tenant) throws Exception {
return doPost("/api/tenant", tenant, Tenant.class);
}
protected void loginDifferentCustomer() throws Exception {
if (savedDifferentCustomer != null) {
login(savedDifferentCustomer.getEmail(), CUSTOMER_USER_PASSWORD);

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

@ -53,9 +53,8 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -584,7 +583,7 @@ public class AlarmControllerTest extends AbstractControllerTest {
Assert.assertEquals(savedUser.getId(), foundAlarm.getAssigneeId());
Assert.assertTrue(foundAlarm.getAssignTs() >= beforeAssignmentTs);
beforeAssignmentTs = System.currentTimeMillis();
long afterAssignmentTs = System.currentTimeMillis();
loginSysAdmin();
@ -592,12 +591,12 @@ public class AlarmControllerTest extends AbstractControllerTest {
loginDifferentTenant();
foundAlarm = Awaitility.await().atMost(TIMEOUT, TimeUnit.SECONDS)
.until(() -> doGet("/api/alarm/info/" + alarmId.getId(), AlarmInfo.class), Objects::nonNull);
Assert.assertNotNull(foundAlarm);
Assert.assertNull(foundAlarm.getAssigneeId());
Assert.assertTrue(foundAlarm.getAssignTs() >= beforeAssignmentTs);
Awaitility.await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> {
AlarmInfo alarmInfo = doGet("/api/alarm/info/" + alarmId.getId(), AlarmInfo.class);
Assert.assertNotNull(alarmInfo);
Assert.assertNull(alarmInfo.getAssigneeId());
Assert.assertTrue(alarmInfo.getAssignTs() >= afterAssignmentTs);
});
}
@Test
@ -631,71 +630,16 @@ public class AlarmControllerTest extends AbstractControllerTest {
Assert.assertEquals(savedUser.getId(), foundAlarm.getAssigneeId());
Assert.assertTrue(foundAlarm.getAssignTs() >= beforeAssignmentTs);
beforeAssignmentTs = System.currentTimeMillis();
long afterAssignmentTs = System.currentTimeMillis();
doDelete("/api/user/" + savedUser.getId().getId()).andExpect(status().isOk());
foundAlarm = Awaitility.await().atMost(TIMEOUT, TimeUnit.SECONDS)
.until(() -> doGet("/api/alarm/info/" + alarmId.getId(), AlarmInfo.class), Objects::nonNull);
Assert.assertNotNull(foundAlarm);
Assert.assertNull(foundAlarm.getAssigneeId());
Assert.assertTrue(foundAlarm.getAssignTs() >= beforeAssignmentTs);
}
@Test
public void testUnassignAlarmOnCustomerRemoving() throws Exception {
createDifferentTenantCustomer();
loginDifferentTenant();
User user = new User();
user.setAuthority(Authority.CUSTOMER_USER);
user.setTenantId(tenantId);
user.setCustomerId(differentTenantCustomerId);
user.setEmail("customerForAssign@thingsboard.org");
User savedUser = createUser(user, "password");
Device device = createDevice("Different customer device", "default", "differentTenantTest");
Device assignedDevice = doPost("/api/customer/" + differentTenantCustomerId.getId()
+ "/device/" + device.getId().getId(), Device.class);
Assert.assertEquals(differentTenantCustomerId, assignedDevice.getCustomerId());
Alarm alarm = Alarm.builder()
.type(TEST_ALARM_TYPE)
.tenantId(savedDifferentTenant.getId())
.customerId(differentTenantCustomerId)
.originator(device.getId())
.severity(AlarmSeverity.MAJOR)
.build();
alarm = doPost("/api/alarm", alarm, Alarm.class);
Assert.assertNotNull(alarm);
AlarmId alarmId = alarm.getId();
alarm = doGet("/api/alarm/info/" + alarmId.getId(), AlarmInfo.class);
Assert.assertNotNull(alarm);
Mockito.reset(tbClusterService, auditLogService);
long beforeAssignmentTs = System.currentTimeMillis();
doPost("/api/alarm/" + alarmId.getId() + "/assign/" + savedUser.getId().getId()).andExpect(status().isOk());
AlarmInfo foundAlarm = doGet("/api/alarm/info/" + alarmId.getId(), AlarmInfo.class);
Assert.assertNotNull(foundAlarm);
Assert.assertEquals(savedUser.getId(), foundAlarm.getAssigneeId());
Assert.assertTrue(foundAlarm.getAssignTs() >= beforeAssignmentTs);
beforeAssignmentTs = System.currentTimeMillis();
Mockito.reset(tbClusterService, auditLogService);
doDelete("/api/customer/" + differentTenantCustomerId.getId()).andExpect(status().isOk());
foundAlarm = Awaitility.await().atMost(TIMEOUT, TimeUnit.SECONDS)
.until(() -> doGet("/api/alarm/info/" + alarmId.getId(), AlarmInfo.class), Objects::nonNull);
Assert.assertNotNull(foundAlarm);
Assert.assertNull(foundAlarm.getAssigneeId());
Assert.assertTrue(foundAlarm.getAssignTs() >= beforeAssignmentTs);
Awaitility.await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> {
AlarmInfo alarmInfo = doGet("/api/alarm/info/" + alarmId.getId(), AlarmInfo.class);
Assert.assertNotNull(alarmInfo);
Assert.assertNull(alarmInfo.getAssigneeId());
Assert.assertTrue(alarmInfo.getAssignTs() >= afterAssignmentTs);
});
}
@Test
@ -1003,27 +947,20 @@ public class AlarmControllerTest extends AbstractControllerTest {
types.remove(0);
}
foundTypes = doGetTyped("/api/alarm/types?pageSize=1024&page=0", new TypeReference<PageData<EntitySubtype>>() {
})
.getData()
.stream()
.map(EntitySubtype::getType)
.collect(Collectors.toList());
Assert.assertEquals(types.size(), foundTypes.size());
Assert.assertEquals(types, foundTypes);
Awaitility.await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> {
List<String> actualTypes = doGetTyped("/api/alarm/types?pageSize=1024&page=0", new TypeReference<PageData<EntitySubtype>>() {})
.getData().stream().map(EntitySubtype::getType).toList();
Assert.assertEquals(types.size(), actualTypes.size());
Assert.assertEquals(types, actualTypes);
});
doDelete("/api/device/" + devices.get(0).getId()).andExpect(status().isOk());
foundTypes = doGetTyped("/api/alarm/types?pageSize=1024&page=0", new TypeReference<PageData<EntitySubtype>>() {
})
.getData()
.stream()
.map(EntitySubtype::getType)
.sorted()
.collect(Collectors.toList());
Assert.assertTrue(foundTypes.isEmpty());
Awaitility.await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> {
List<String> actualTypes = doGetTyped("/api/alarm/types?pageSize=1024&page=0", new TypeReference<PageData<EntitySubtype>>() {})
.getData().stream().map(EntitySubtype::getType).sorted().toList();
Assert.assertTrue(actualTypes.isEmpty());
});
}
}

24
application/src/test/java/org/thingsboard/server/controller/AssetControllerTest.java

@ -42,6 +42,7 @@ import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.page.PageData;
@ -55,8 +56,10 @@ import org.thingsboard.server.dao.service.DaoSqlTest;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
@ -87,7 +90,7 @@ public class AssetControllerTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
savedTenant = saveTenant(tenant);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
@ -104,8 +107,7 @@ public class AssetControllerTest extends AbstractControllerTest {
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant.getId());
}
@Test
@ -312,8 +314,9 @@ public class AssetControllerTest extends AbstractControllerTest {
alarm = doPost("/api/alarm", alarm, Alarm.class);
Assert.assertNotNull(alarm);
AlarmId alarmId = alarm.getId();
AlarmInfo foundAlarm = doGet("/api/alarm/info/" + alarm.getId(), AlarmInfo.class);
AlarmInfo foundAlarm = doGet("/api/alarm/info/" + alarmId, AlarmInfo.class);
Assert.assertNotNull(foundAlarm);
doDelete("/api/asset/" + savedAsset.getId().getId().toString())
@ -324,9 +327,11 @@ public class AssetControllerTest extends AbstractControllerTest {
.andExpect(status().isNotFound())
.andExpect(statusReason(containsString(msgErrorNoFound("Asset", assetIdStr))));
doGet("/api/alarm/info/" + alarm.getId())
.andExpect(status().isNotFound())
.andExpect(statusReason(containsString(msgErrorNoFound("Alarm", alarm.getId().getId().toString()))));
await().atMost(30, TimeUnit.SECONDS).untilAsserted(() -> {
doGet("/api/alarm/info/" + alarmId)
.andExpect(status().isNotFound())
.andExpect(statusReason(containsString(msgErrorNoFound("Alarm", alarmId.getId().toString()))));
});
}
@Test
@ -548,7 +553,7 @@ public class AssetControllerTest extends AbstractControllerTest {
Tenant tenant2 = new Tenant();
tenant2.setTitle("Different tenant");
Tenant savedTenant2 = doPost("/api/tenant", tenant2, Tenant.class);
Tenant savedTenant2 = saveTenant(tenant2);
Assert.assertNotNull(savedTenant2);
User tenantAdmin2 = new User();
@ -582,8 +587,7 @@ public class AssetControllerTest extends AbstractControllerTest {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant2.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant2.getId());
}
@Test

5
application/src/test/java/org/thingsboard/server/controller/AssetProfileControllerTest.java

@ -82,7 +82,7 @@ public class AssetProfileControllerTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
savedTenant = saveTenant(tenant);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
@ -99,8 +99,7 @@ public class AssetProfileControllerTest extends AbstractControllerTest {
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant.getId());
}
@Test

86
application/src/test/java/org/thingsboard/server/controller/AuditLogControllerTest.java

@ -15,7 +15,9 @@
*/
package org.thingsboard.server.controller;
import com.datastax.oss.driver.api.core.uuid.Uuids;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
@ -28,6 +30,7 @@ import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.audit.AuditLog;
import org.thingsboard.server.common.data.id.AuditLogId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.TimePageLink;
@ -38,19 +41,21 @@ import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.dao.sqlts.insert.sql.SqlPartitioningRepository;
import org.thingsboard.server.service.ttl.AuditLogsCleanUpService;
import java.time.LocalDate;
import java.time.ZoneOffset;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import static org.apache.commons.lang3.time.DateFormatUtils.ISO_8601_EXTENDED_DATETIME_TIME_ZONE_FORMAT;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.willReturn;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Slf4j
@DaoSqlTest
public class AuditLogControllerTest extends AbstractControllerTest {
@ -61,7 +66,7 @@ public class AuditLogControllerTest extends AbstractControllerTest {
private AuditLogDao auditLogDao;
@SpyBean
private SqlPartitioningRepository partitioningRepository;
@Autowired
@SpyBean
private AuditLogsCleanUpService auditLogsCleanUpService;
@Value("#{${sql.audit_logs.partition_size} * 60 * 60 * 1000}")
@ -75,7 +80,7 @@ public class AuditLogControllerTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
savedTenant = saveTenant(tenant);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
@ -92,13 +97,12 @@ public class AuditLogControllerTest extends AbstractControllerTest {
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant.getId());
}
@Test
public void testAuditLogs() throws Exception {
for (int i = 0; i < 178; i++) {
for (int i = 0; i < 11; i++) {
Device device = new Device();
device.setName("Device" + i);
device.setType("default");
@ -106,7 +110,7 @@ public class AuditLogControllerTest extends AbstractControllerTest {
}
List<AuditLog> loadedAuditLogs = new ArrayList<>();
TimePageLink pageLink = new TimePageLink(23);
TimePageLink pageLink = new TimePageLink(5);
PageData<AuditLog> pageData;
do {
pageData = doGetTypedWithTimePageLink("/api/audit/logs?",
@ -118,10 +122,10 @@ public class AuditLogControllerTest extends AbstractControllerTest {
}
} while (pageData.hasNext());
Assert.assertEquals(178, loadedAuditLogs.size());
Assert.assertEquals(11, loadedAuditLogs.size());
loadedAuditLogs = new ArrayList<>();
pageLink = new TimePageLink(23);
pageLink = new TimePageLink(5);
do {
pageData = doGetTypedWithTimePageLink("/api/audit/logs/customer/" + ModelConstants.NULL_UUID + "?",
new TypeReference<PageData<AuditLog>>() {
@ -132,10 +136,10 @@ public class AuditLogControllerTest extends AbstractControllerTest {
}
} while (pageData.hasNext());
Assert.assertEquals(178, loadedAuditLogs.size());
Assert.assertEquals(11, loadedAuditLogs.size());
loadedAuditLogs = new ArrayList<>();
pageLink = new TimePageLink(23);
pageLink = new TimePageLink(5);
do {
pageData = doGetTypedWithTimePageLink("/api/audit/logs/user/" + tenantAdmin.getId().getId().toString() + "?",
new TypeReference<PageData<AuditLog>>() {
@ -146,7 +150,7 @@ public class AuditLogControllerTest extends AbstractControllerTest {
}
} while (pageData.hasNext());
Assert.assertEquals(178, loadedAuditLogs.size());
Assert.assertEquals(11, loadedAuditLogs.size());
}
@Test
@ -155,13 +159,13 @@ public class AuditLogControllerTest extends AbstractControllerTest {
device.setName("Device name");
device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
for (int i = 0; i < 178; i++) {
for (int i = 0; i < 11; i++) {
savedDevice.setName("Device name" + i);
doPost("/api/device", savedDevice, Device.class);
}
List<AuditLog> loadedAuditLogs = new ArrayList<>();
TimePageLink pageLink = new TimePageLink(23);
TimePageLink pageLink = new TimePageLink(5);
PageData<AuditLog> pageData;
do {
pageData = doGetTypedWithTimePageLink("/api/audit/logs/entity/DEVICE/" + savedDevice.getId().getId() + "?",
@ -173,54 +177,61 @@ public class AuditLogControllerTest extends AbstractControllerTest {
}
} while (pageData.hasNext());
Assert.assertEquals(179, loadedAuditLogs.size());
Assert.assertEquals(11 + 1, loadedAuditLogs.size());
}
@Test
public void whenSavingNewAuditLog_thenCheckAndCreatePartitionIfNotExists() {
public void whenSavingNewAuditLog_thenCheckAndCreatePartitionIfNotExists() throws ParseException {
long entityTs = ISO_8601_EXTENDED_DATETIME_TIME_ZONE_FORMAT.parse("2024-01-01T01:43:11Z").getTime();
reset(partitioningRepository);
AuditLog auditLog = createAuditLog(ActionType.LOGIN, tenantAdminUserId);
AuditLog auditLog = createAuditLog(ActionType.LOGIN, tenantAdminUserId, entityTs);
verify(partitioningRepository).createPartitionIfNotExists(eq("audit_log"), eq(auditLog.getCreatedTime()), eq(partitionDurationInMs));
List<Long> partitions = partitioningRepository.fetchPartitions("audit_log");
assertThat(partitions).singleElement().satisfies(partitionStartTs -> {
assertThat(partitionStartTs).isEqualTo(partitioningRepository.calculatePartitionStartTime(auditLog.getCreatedTime(), partitionDurationInMs));
});
assertThat(partitions).contains(partitioningRepository.calculatePartitionStartTime(auditLog.getCreatedTime(), partitionDurationInMs));
}
@Test
public void whenCleaningUpAuditLogsByTtl_thenDropOldPartitions() {
long oldAuditLogTs = LocalDate.of(2020, 10, 1).atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli();
long partitionStartTs = partitioningRepository.calculatePartitionStartTime(oldAuditLogTs, partitionDurationInMs);
public void whenCleaningUpAuditLogsByTtl_thenDropOldPartitions() throws ParseException {
final long oldAuditLogTs = ISO_8601_EXTENDED_DATETIME_TIME_ZONE_FORMAT.parse("2020-10-01T00:00:00Z").getTime();
final long currentTimeMillis = oldAuditLogTs + TimeUnit.SECONDS.toMillis(auditLogsTtlInSec) * 2;
final long partitionStartTs = partitioningRepository.calculatePartitionStartTime(oldAuditLogTs, partitionDurationInMs);
partitioningRepository.createPartitionIfNotExists("audit_log", oldAuditLogTs, partitionDurationInMs);
List<Long> partitions = partitioningRepository.fetchPartitions("audit_log");
assertThat(partitions).contains(partitionStartTs);
willReturn(currentTimeMillis).given(auditLogsCleanUpService).getCurrentTimeMillis();
auditLogsCleanUpService.cleanUp();
partitions = partitioningRepository.fetchPartitions("audit_log");
assertThat(partitions).doesNotContain(partitionStartTs);
assertThat(partitions).allSatisfy(partitionsStart -> {
assertThat(partitions).as("partitions cleared").doesNotContain(partitionStartTs);
assertThat(partitions).as("only newer partitions left").allSatisfy(partitionsStart -> {
long partitionEndTs = partitionsStart + partitionDurationInMs;
assertThat(partitionEndTs).isGreaterThan(System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(auditLogsTtlInSec));
assertThat(partitionEndTs).isGreaterThan(currentTimeMillis - TimeUnit.SECONDS.toMillis(auditLogsTtlInSec));
});
}
@Test
public void whenSavingAuditLogAndPartitionSaveErrorOccurred_thenSaveAuditLogAnyway() throws Exception {
// creating partition bigger than sql.audit_logs.partition_size
partitioningRepository.createPartitionIfNotExists("audit_log", System.currentTimeMillis(), TimeUnit.DAYS.toMillis(7));
long entityTs = ISO_8601_EXTENDED_DATETIME_TIME_ZONE_FORMAT.parse("2024-04-29T07:43:11Z").getTime();
partitioningRepository.createPartitionIfNotExists("audit_log", entityTs, TimeUnit.DAYS.toMillis(7));
List<Long> partitions = partitioningRepository.fetchPartitions("audit_log");
assertThat(partitions).size().isOne();
partitioningRepository.cleanupPartitionsCache("audit_log", System.currentTimeMillis(), 0);
log.warn("entityTs [{}], fetched partitions {}", entityTs, partitions);
assertThat(partitions).contains(ISO_8601_EXTENDED_DATETIME_TIME_ZONE_FORMAT.parse("2024-04-29T00:00:00Z").getTime());
partitioningRepository.cleanupPartitionsCache("audit_log", entityTs, 0);
assertDoesNotThrow(() -> {
// expecting partition overlap error on partition save
createAuditLog(ActionType.LOGIN, tenantAdminUserId);
createAuditLog(ActionType.LOGIN, tenantAdminUserId, entityTs);
});
assertThat(partitioningRepository.fetchPartitions("audit_log")).isEqualTo(partitions);
assertThat(partitioningRepository.fetchPartitions("audit_log"))
.contains(ISO_8601_EXTENDED_DATETIME_TIME_ZONE_FORMAT.parse("2024-04-29T00:00:00Z").getTime());;
}
private AuditLog createAuditLog(ActionType actionType, EntityId entityId) {
private AuditLog createAuditLog(ActionType actionType, EntityId entityId, long entityTs) {
AuditLog auditLog = new AuditLog();
auditLog.setTenantId(tenantId);
auditLog.setCustomerId(null);
@ -228,6 +239,13 @@ public class AuditLogControllerTest extends AbstractControllerTest {
auditLog.setEntityId(entityId);
auditLog.setUserName(tenantAdmin.getEmail());
auditLog.setActionType(actionType);
if (entityTs > 0) {
UUID uuid = Uuids.startOf(entityTs);
auditLog.setId(new AuditLogId(uuid));
auditLog.setCreatedTime(Uuids.unixTimestamp(uuid));
}
return auditLogDao.save(tenantId, auditLog);
}

5
application/src/test/java/org/thingsboard/server/controller/ComponentDescriptorControllerTest.java

@ -47,7 +47,7 @@ public class ComponentDescriptorControllerTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
savedTenant = saveTenant(tenant);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
@ -64,8 +64,7 @@ public class ComponentDescriptorControllerTest extends AbstractControllerTest {
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant.getId());
}
@Test

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

@ -85,7 +85,7 @@ public class CustomerControllerTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
savedTenant = saveTenant(tenant);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
@ -104,8 +104,7 @@ public class CustomerControllerTest extends AbstractControllerTest {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant.getId());
}
@Test
@ -154,7 +153,7 @@ public class CustomerControllerTest extends AbstractControllerTest {
.andExpect(statusReason(containsString(msgError)));
customer.setTenantId(savedTenant.getId());
testNotifyEntityEqualsOneTimeServiceNeverError(customer,savedTenant.getId(),
testNotifyEntityEqualsOneTimeServiceNeverError(customer, savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError));
Mockito.reset(tbClusterService, auditLogService);
@ -165,7 +164,7 @@ public class CustomerControllerTest extends AbstractControllerTest {
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString(msgError)));
testNotifyEntityEqualsOneTimeServiceNeverError(customer,savedTenant.getId(),
testNotifyEntityEqualsOneTimeServiceNeverError(customer, savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError));
Mockito.reset(tbClusterService, auditLogService);
@ -176,7 +175,7 @@ public class CustomerControllerTest extends AbstractControllerTest {
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString(msgError)));
testNotifyEntityEqualsOneTimeServiceNeverError(customer,savedTenant.getId(),
testNotifyEntityEqualsOneTimeServiceNeverError(customer, savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError));
Mockito.reset(tbClusterService, auditLogService);
@ -187,7 +186,7 @@ public class CustomerControllerTest extends AbstractControllerTest {
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString(msgError)));
testNotifyEntityEqualsOneTimeServiceNeverError(customer,savedTenant.getId(),
testNotifyEntityEqualsOneTimeServiceNeverError(customer, savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError));
Mockito.reset(tbClusterService, auditLogService);
@ -198,7 +197,7 @@ public class CustomerControllerTest extends AbstractControllerTest {
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString(msgError)));
testNotifyEntityEqualsOneTimeServiceNeverError(customer,savedTenant.getId(),
testNotifyEntityEqualsOneTimeServiceNeverError(customer, savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError));
Mockito.reset(tbClusterService, auditLogService);
@ -209,7 +208,7 @@ public class CustomerControllerTest extends AbstractControllerTest {
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString(msgError)));
testNotifyEntityEqualsOneTimeServiceNeverError(customer,savedTenant.getId(),
testNotifyEntityEqualsOneTimeServiceNeverError(customer, savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError));
}
@ -293,7 +292,7 @@ public class CustomerControllerTest extends AbstractControllerTest {
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString(msgError)));
testNotifyEntityEqualsOneTimeServiceNeverError(customer,savedTenant.getId(),
testNotifyEntityEqualsOneTimeServiceNeverError(customer, savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError));
}

20
application/src/test/java/org/thingsboard/server/controller/DashboardControllerTest.java

@ -77,7 +77,7 @@ public class DashboardControllerTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
savedTenant = saveTenant(tenant);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
@ -94,8 +94,7 @@ public class DashboardControllerTest extends AbstractControllerTest {
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant.getId());
}
@Test
@ -166,7 +165,7 @@ public class DashboardControllerTest extends AbstractControllerTest {
@Test
public void testSaveDashboardWithEmptyTitle() throws Exception {
Dashboard dashboard = new Dashboard();
String msgError = "Dashboard title " + msgErrorShouldBeSpecified;;
String msgError = "Dashboard title " + msgErrorShouldBeSpecified;
Mockito.reset(tbClusterService, auditLogService);
@ -196,7 +195,7 @@ public class DashboardControllerTest extends AbstractControllerTest {
Assert.assertTrue(assignedDashboard.getAssignedCustomers().contains(savedCustomer.toShortCustomerInfo()));
testNotifyEntityAllOneTimeLogEntityActionEntityEqClass(assignedDashboard, assignedDashboard.getId(), assignedDashboard.getId(),
savedTenant.getId(), savedCustomer.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ASSIGNED_TO_CUSTOMER,
savedTenant.getId(), savedCustomer.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ASSIGNED_TO_CUSTOMER,
ActionType.UPDATED, assignedDashboard.getId().getId().toString(), savedCustomer.getId().getId().toString(), savedCustomer.getTitle());
Dashboard foundDashboard = doGet("/api/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
@ -239,8 +238,8 @@ public class DashboardControllerTest extends AbstractControllerTest {
Assert.assertTrue(publicCustomer.isPublic());
testNotifyEntityAllOneTimeLogEntityActionEntityEqClass(assignedDashboard, assignedDashboard.getId(), assignedDashboard.getId(),
savedTenant.getId(), publicCustomer.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ASSIGNED_TO_CUSTOMER,
ActionType.UPDATED, assignedDashboard .getId().getId().toString(), publicCustomer.getId().getId().toString(), publicCustomer.getTitle());
savedTenant.getId(), publicCustomer.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ASSIGNED_TO_CUSTOMER,
ActionType.UPDATED, assignedDashboard.getId().getId().toString(), publicCustomer.getId().getId().toString(), publicCustomer.getTitle());
Dashboard foundDashboard = doGet("/api/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
Assert.assertTrue(foundDashboard.getAssignedCustomers().contains(publicCustomer.toShortCustomerInfo()));
@ -283,7 +282,7 @@ public class DashboardControllerTest extends AbstractControllerTest {
Tenant tenant2 = new Tenant();
tenant2.setTitle("Different tenant");
Tenant savedTenant2 = doPost("/api/tenant", tenant2, Tenant.class);
Tenant savedTenant2 = saveTenant(tenant2);
Assert.assertNotNull(savedTenant2);
User tenantAdmin2 = new User();
@ -321,8 +320,7 @@ public class DashboardControllerTest extends AbstractControllerTest {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant2.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant2.getId());
}
@Test
@ -482,7 +480,7 @@ public class DashboardControllerTest extends AbstractControllerTest {
testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(new Dashboard(), new Dashboard(),
savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.ADDED, cntEntity, cntEntity, cntEntity*2);
ActionType.ADDED, cntEntity, cntEntity, cntEntity * 2);
List<DashboardInfo> loadedDashboards = new ArrayList<>();
PageLink pageLink = new PageLink(21);

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

@ -142,7 +142,7 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
savedTenant = saveTenant(tenant);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
@ -187,8 +187,7 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest {
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId())
.andExpect(status().isOk());
deleteTenant(savedTenant.getId());
}
@Test

18
application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java

@ -132,7 +132,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
savedTenant = saveTenant(tenant);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
@ -151,8 +151,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId())
.andExpect(status().isOk());
deleteTenant(savedTenant.getId());
}
@Test
@ -687,7 +686,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
Tenant tenant2 = new Tenant();
tenant2.setTitle("Different tenant");
Tenant savedTenant2 = doPost("/api/tenant", tenant2, Tenant.class);
Tenant savedTenant2 = saveTenant(tenant2);
Assert.assertNotNull(savedTenant2);
User tenantAdmin2 = new User();
@ -721,9 +720,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
testNotificationUpdateGatewayNever();
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant2.getId().getId())
.andExpect(status().isOk());
deleteTenant(savedTenant2.getId());
}
@Test
@ -914,7 +911,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAnyAdditionalInfoAny(new Device(), new Device(),
savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.DELETED, ActionType.DELETED, cntEntity, cntEntity,1);
ActionType.DELETED, ActionType.DELETED, cntEntity, cntEntity, 1);
testNotificationUpdateGatewayNever();
}
@ -1319,7 +1316,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("Different tenant");
Tenant savedDifferentTenant = doPost("/api/tenant", tenant, Tenant.class);
Tenant savedDifferentTenant = saveTenant(tenant);
Assert.assertNotNull(savedDifferentTenant);
User user = new User();
@ -1380,8 +1377,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
.andExpect(statusReason(containsString(msgErrorNoFound("Device", savedAnotherDevice.getId().getId().toString()))));
loginSysAdmin();
doDelete("/api/tenant/" + savedDifferentTenant.getId().getId())
.andExpect(status().isOk());
deleteTenant(savedDifferentTenant.getId());
}
@Test

5
application/src/test/java/org/thingsboard/server/controller/DeviceProfileControllerTest.java

@ -93,7 +93,7 @@ public class DeviceProfileControllerTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
savedTenant = saveTenant(tenant);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
@ -110,8 +110,7 @@ public class DeviceProfileControllerTest extends AbstractControllerTest {
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant.getId());
}
@Test

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

@ -361,7 +361,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
Tenant tenant2 = new Tenant();
tenant2.setTitle("Different tenant");
Tenant savedTenant2 = doPost("/api/tenant", tenant2, Tenant.class);
Tenant savedTenant2 = saveTenant(tenant2);
Assert.assertNotNull(savedTenant2);
User tenantAdmin2 = new User();
@ -394,8 +394,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant2.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant2.getId());
}
@Test

75
application/src/test/java/org/thingsboard/server/controller/EdgeEventControllerTest.java

@ -28,7 +28,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.test.context.TestPropertySource;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.edge.Edge;
@ -39,6 +39,7 @@ import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.dao.edge.EdgeEventDao;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.dao.sqlts.insert.sql.SqlPartitioningRepository;
@ -89,21 +90,22 @@ public class EdgeEventControllerTest extends AbstractControllerTest {
Edge edge = constructEdge("TestEdge", "default");
edge = doPost("/api/edge", edge, Edge.class);
final EdgeId edgeId = edge.getId();
awaitForEdgeTemplateRootRuleChainToAssignToEdge(edgeId);
// simulate edge activation
ObjectNode attributes = JacksonUtil.newObjectNode();
attributes.put("active", true);
doPost("/api/plugins/telemetry/EDGE/" + edge.getId() + "/attributes/" + DataConstants.SERVER_SCOPE, attributes);
doPost("/api/plugins/telemetry/EDGE/" + edge.getId() + "/attributes/" + AttributeScope.SERVER_SCOPE, attributes);
Device device = constructDevice("TestDevice", "default");
Device savedDevice = doPost("/api/device", device, Device.class);
final EdgeId edgeId = edge.getId();
doPost("/api/edge/" + edgeId.toString() + "/device/" + savedDevice.getId().toString(), Device.class);
doPost("/api/edge/" + edgeId + "/device/" + savedDevice.getId(), Device.class);
Asset asset = constructAsset("TestAsset", "default");
Asset savedAsset = doPost("/api/asset", asset, Asset.class);
doPost("/api/edge/" + edgeId.toString() + "/asset/" + savedAsset.getId().toString(), Asset.class);
doPost("/api/edge/" + edgeId + "/asset/" + savedAsset.getId(), Asset.class);
EntityRelation relation = new EntityRelation(savedAsset.getId(), savedDevice.getId(), EntityRelation.CONTAINS_TYPE);
@ -114,31 +116,12 @@ public class EdgeEventControllerTest extends AbstractControllerTest {
awaitForNumberOfEdgeEvents(edgeId, 3);
List<EdgeEvent> edgeEvents = findEdgeEvents(edgeId);
Assert.assertTrue(popEdgeEvent(edgeEvents, EdgeEventType.DEVICE)); // TestDevice
Assert.assertTrue(popEdgeEvent(edgeEvents, EdgeEventType.ASSET)); // TestAsset
Assert.assertTrue(popEdgeEvent(edgeEvents, EdgeEventType.DEVICE));
Assert.assertTrue(popEdgeEvent(edgeEvents, EdgeEventType.ASSET));
Assert.assertTrue(popEdgeEvent(edgeEvents, EdgeEventType.RELATION));
Assert.assertTrue(edgeEvents.isEmpty());
}
private boolean popEdgeEvent(List<EdgeEvent> edgeEvents, EdgeEventType edgeEventType) {
for (EdgeEvent edgeEvent : edgeEvents) {
if (edgeEventType.equals(edgeEvent.getType())) {
edgeEvents.remove(edgeEvent);
return true;
}
}
return false;
}
private void awaitForNumberOfEdgeEvents(EdgeId edgeId, int expectedNumber) {
Awaitility.await()
.atMost(30, TimeUnit.SECONDS)
.until(() -> {
List<EdgeEvent> edgeEvents = findEdgeEvents(edgeId);
return edgeEvents.size() == expectedNumber;
});
}
@Test
public void saveEdgeEvent_thenCreatePartitionIfNotExist() {
reset(partitioningRepository);
@ -167,12 +150,46 @@ public class EdgeEventControllerTest extends AbstractControllerTest {
});
}
private boolean popEdgeEvent(List<EdgeEvent> edgeEvents, EdgeEventType edgeEventType) {
for (EdgeEvent edgeEvent : edgeEvents) {
if (edgeEventType.equals(edgeEvent.getType())) {
edgeEvents.remove(edgeEvent);
return true;
}
}
return false;
}
private void awaitForNumberOfEdgeEvents(EdgeId edgeId, int expectedNumber) {
Awaitility.await()
.atMost(TIMEOUT, TimeUnit.SECONDS)
.until(() -> {
List<EdgeEvent> edgeEvents = findEdgeEvents(edgeId);
return edgeEvents.size() == expectedNumber;
});
}
private List<EdgeEvent> findEdgeEvents(EdgeId edgeId) throws Exception {
return doGetTypedWithTimePageLink("/api/edge/" + edgeId.toString() + "/events?",
return doGetTypedWithTimePageLink("/api/edge/" + edgeId + "/events?",
new TypeReference<PageData<EdgeEvent>>() {
}, new TimePageLink(10)).getData();
}
private void awaitForEdgeTemplateRootRuleChainToAssignToEdge(EdgeId edgeId) {
Awaitility.await()
.atMost(TIMEOUT, TimeUnit.SECONDS)
.until(() -> {
List<RuleChain> ruleChains = getEdgeRuleChains(edgeId);
return ruleChains.size() == 1;
});
}
private List<RuleChain> getEdgeRuleChains(EdgeId edgeId) throws Exception {
return doGetTypedWithTimePageLink("/api/edge/" + edgeId + "/ruleChains?",
new TypeReference<PageData<RuleChain>>() {
}, new TimePageLink(10)).getData();
}
private Device constructDevice(String name, String type) {
Device device = new Device();
device.setName(name);

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

@ -52,6 +52,7 @@ import org.thingsboard.server.common.data.query.EntityTypeFilter;
import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.common.data.query.KeyFilter;
import org.thingsboard.server.common.data.query.NumericFilterPredicate;
import org.thingsboard.server.common.data.query.StringFilterPredicate;
import org.thingsboard.server.common.data.query.TsValue;
import org.thingsboard.server.common.data.queue.QueueStats;
import org.thingsboard.server.common.data.security.Authority;
@ -83,7 +84,7 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
savedTenant = saveTenant(tenant);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
@ -99,9 +100,7 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
@After
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant.getId());
}
@Test
@ -644,4 +643,166 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
Assert.assertEquals(97, count.longValue());
}
@Test
public void testFindDevicesCountByOwnerNameAndOwnerType() throws Exception {
loginTenantAdmin();
int numOfDevices = 8;
for (int i = 0; i < numOfDevices; i++) {
Device device = new Device();
String name = "Device" + i;
device.setName(name);
device.setType("default");
Device savedDevice = doPost("/api/device?accessToken=" + name, device, Device.class);
JsonNode content = JacksonUtil.toJsonNode("{\"alarmActiveTime\": 1" + i + "}");
doPost("/api/plugins/telemetry/" + EntityType.DEVICE.name() + "/" + savedDevice.getUuidId() + "/SERVER_SCOPE", content)
.andExpect(status().isOk());
}
DeviceTypeFilter filter = new DeviceTypeFilter();
filter.setDeviceTypes(List.of("default"));
filter.setDeviceNameFilter("");
KeyFilter activeAlarmTimeFilter = getServerAttributeNumericGreaterThanKeyFilter("alarmActiveTime", 5);
KeyFilter activeAlarmTimeToLongFilter = getServerAttributeNumericGreaterThanKeyFilter("alarmActiveTime", 30);
KeyFilter tenantOwnerNameFilter = getEntityFieldStringEqualToKeyFilter("ownerName", TEST_TENANT_NAME);
KeyFilter wrongOwnerNameFilter = getEntityFieldStringEqualToKeyFilter("ownerName", "wrongName");
KeyFilter tenantOwnerTypeFilter = getEntityFieldStringEqualToKeyFilter("ownerType", "TENANT");
KeyFilter customerOwnerTypeFilter = getEntityFieldStringEqualToKeyFilter("ownerType", "CUSTOMER");
// all devices with ownerName = TEST TENANT
EntityCountQuery query = new EntityCountQuery(filter, List.of(activeAlarmTimeFilter, tenantOwnerNameFilter));
checkEntitiesCount(query, numOfDevices);
// all devices with ownerName = TEST TENANT
EntityCountQuery activeAlarmTimeToLongQuery = new EntityCountQuery(filter, List.of(activeAlarmTimeToLongFilter, tenantOwnerNameFilter));
checkEntitiesCount(activeAlarmTimeToLongQuery, 0);
// all devices with wrong ownerName
EntityCountQuery wrongTenantNameQuery = new EntityCountQuery(filter, List.of(activeAlarmTimeFilter, wrongOwnerNameFilter));
checkEntitiesCount(wrongTenantNameQuery, 0);
// all devices with owner type = TENANT
EntityCountQuery tenantEntitiesQuery = new EntityCountQuery(filter, List.of(activeAlarmTimeFilter, tenantOwnerTypeFilter));
checkEntitiesCount(tenantEntitiesQuery, numOfDevices);
// all devices with owner type = CUSTOMER
EntityCountQuery customerEntitiesQuery = new EntityCountQuery(filter, List.of(activeAlarmTimeFilter, customerOwnerTypeFilter));
checkEntitiesCount(customerEntitiesQuery, 0);
}
@Test
public void testFindDevicesByOwnerNameAndOwnerType() throws Exception {
loginTenantAdmin();
int numOfDevices = 3;
for (int i = 0; i < numOfDevices; i++) {
Device device = new Device();
String name = "Device" + i;
device.setName(name);
device.setType("default");
Device savedDevice = doPost("/api/device?accessToken=" + name, device, Device.class);
JsonNode content = JacksonUtil.toJsonNode("{\"alarmActiveTime\": 1" + i + "}");
doPost("/api/plugins/telemetry/" + EntityType.DEVICE.name() + "/" + savedDevice.getUuidId() + "/SERVER_SCOPE", content)
.andExpect(status().isOk());
}
DeviceTypeFilter filter = new DeviceTypeFilter();
filter.setDeviceTypes(List.of("default"));
filter.setDeviceNameFilter("");
KeyFilter activeAlarmTimeFilter = getServerAttributeNumericGreaterThanKeyFilter("alarmActiveTime", 5);
KeyFilter tenantOwnerNameFilter = getEntityFieldStringEqualToKeyFilter("ownerName", TEST_TENANT_NAME);
KeyFilter wrongOwnerNameFilter = getEntityFieldStringEqualToKeyFilter("ownerName", "wrongName");
KeyFilter tenantOwnerTypeFilter = getEntityFieldStringEqualToKeyFilter("ownerType", "TENANT");
KeyFilter customerOwnerTypeFilter = getEntityFieldStringEqualToKeyFilter("ownerType", "CUSTOMER");
EntityDataSortOrder sortOrder = new EntityDataSortOrder(
new EntityKey(EntityKeyType.ENTITY_FIELD, "createdTime"), EntityDataSortOrder.Direction.ASC
);
EntityDataPageLink pageLink = new EntityDataPageLink(10, 0, null, sortOrder);
List<EntityKey> entityFields = List.of(new EntityKey(EntityKeyType.ENTITY_FIELD, "name"), new EntityKey(EntityKeyType.ENTITY_FIELD, "ownerName"),
new EntityKey(EntityKeyType.ENTITY_FIELD, "ownerType"));
List<EntityKey> latestValues = Collections.singletonList(new EntityKey(EntityKeyType.ATTRIBUTE, "alarmActiveTime"));
// all devices with ownerName = TEST TENANT
EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, List.of(activeAlarmTimeFilter, tenantOwnerNameFilter));
checkEntitiesByQuery(query, numOfDevices, TEST_TENANT_NAME, "TENANT");
// all devices with wrong ownerName
EntityDataQuery wrongTenantNameQuery = new EntityDataQuery(filter, pageLink, entityFields, latestValues, List.of(activeAlarmTimeFilter, wrongOwnerNameFilter));
checkEntitiesByQuery(wrongTenantNameQuery, 0, null, null);
// all devices with owner type = TENANT
EntityDataQuery tenantEntitiesQuery = new EntityDataQuery(filter, pageLink, entityFields, latestValues, List.of(activeAlarmTimeFilter, tenantOwnerTypeFilter));
checkEntitiesByQuery(tenantEntitiesQuery, numOfDevices, TEST_TENANT_NAME, "TENANT");
// all devices with owner type = CUSTOMER
EntityDataQuery customerEntitiesQuery = new EntityDataQuery(filter, pageLink, entityFields, latestValues, List.of(activeAlarmTimeFilter, customerOwnerTypeFilter));
checkEntitiesByQuery(customerEntitiesQuery, 0, null, null);
}
private void checkEntitiesByQuery(EntityDataQuery query, int expectedNumOfDevices, String expectedOwnerName, String expectedOwnerType) throws Exception {
Awaitility.await()
.alias("data by query")
.atMost(30, TimeUnit.SECONDS)
.until(() -> {
var data = doPostWithTypedResponse("/api/entitiesQuery/find", query, new TypeReference<PageData<EntityData>>() {});
var loadedEntities = new ArrayList<>(data.getData());
return loadedEntities.size() == expectedNumOfDevices;
});
if (expectedNumOfDevices == 0) {
return;
}
var data = doPostWithTypedResponse("/api/entitiesQuery/find", query, new TypeReference<PageData<EntityData>>() {});
var loadedEntities = new ArrayList<>(data.getData());
Assert.assertEquals(expectedNumOfDevices, loadedEntities.size());
for (int i = 0; i < expectedNumOfDevices; i++) {
var entity = loadedEntities.get(i);
String name = entity.getLatest().get(EntityKeyType.ENTITY_FIELD).getOrDefault("name", new TsValue(0, "Invalid")).getValue();
String ownerName = entity.getLatest().get(EntityKeyType.ENTITY_FIELD).getOrDefault("ownerName", new TsValue(0, "Invalid")).getValue();
String ownerType = entity.getLatest().get(EntityKeyType.ENTITY_FIELD).getOrDefault("ownerType", new TsValue(0, "Invalid")).getValue();
String alarmActiveTime = entity.getLatest().get(EntityKeyType.ATTRIBUTE).getOrDefault("alarmActiveTime", new TsValue(0, "-1")).getValue();
Assert.assertEquals("Device" + i, name);
Assert.assertEquals( expectedOwnerName, ownerName);
Assert.assertEquals( expectedOwnerType, ownerType);
Assert.assertEquals("1" + i, alarmActiveTime);
}
}
private void checkEntitiesCount(EntityCountQuery query, int expectedNumOfDevices) {
Awaitility.await()
.alias("count by query")
.atMost(30, TimeUnit.SECONDS)
.until(() -> {
var count = doPost("/api/entitiesQuery/count", query, Integer.class);
return count == expectedNumOfDevices;
});
}
private KeyFilter getEntityFieldStringEqualToKeyFilter(String keyName, String value) {
KeyFilter tenantOwnerNameFilter = new KeyFilter();
tenantOwnerNameFilter.setKey(new EntityKey(EntityKeyType.ENTITY_FIELD, keyName));
StringFilterPredicate ownerNamePredicate = new StringFilterPredicate();
ownerNamePredicate.setValue(FilterPredicateValue.fromString(value));
ownerNamePredicate.setOperation(StringFilterPredicate.StringOperation.EQUAL);
tenantOwnerNameFilter.setPredicate(ownerNamePredicate);
return tenantOwnerNameFilter;
}
private KeyFilter getServerAttributeNumericGreaterThanKeyFilter(String attribute, int value) {
KeyFilter numericFilter = new KeyFilter();
numericFilter.setKey(new EntityKey(EntityKeyType.SERVER_ATTRIBUTE, attribute));
NumericFilterPredicate predicate = new NumericFilterPredicate();
predicate.setValue(FilterPredicateValue.fromDouble(value));
predicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
numericFilter.setPredicate(predicate);
return numericFilter;
}
}

5
application/src/test/java/org/thingsboard/server/controller/EntityRelationControllerTest.java

@ -72,7 +72,7 @@ public class EntityRelationControllerTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("Test tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
savedTenant = saveTenant(tenant);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
@ -93,8 +93,7 @@ public class EntityRelationControllerTest extends AbstractControllerTest {
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant.getId());
}
@Test

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

@ -370,7 +370,7 @@ public class EntityViewControllerTest extends AbstractControllerTest {
loginSysAdmin();
Tenant tenant2 = getNewTenant("Different tenant");
Tenant savedTenant2 = doPost("/api/tenant", tenant2, Tenant.class);
Tenant savedTenant2 = saveTenant(tenant2);
Assert.assertNotNull(savedTenant2);
User tenantAdmin2 = new User();
@ -398,8 +398,7 @@ public class EntityViewControllerTest extends AbstractControllerTest {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant2.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant2.getId());
}
@Test
@ -431,7 +430,7 @@ public class EntityViewControllerTest extends AbstractControllerTest {
testNotifyEntityBroadcastEntityStateChangeEventMany(new EntityView(), new EntityView(),
tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL,
ActionType.ASSIGNED_TO_CUSTOMER, ActionType.UPDATED, cntEntity, cntEntity,
cntEntity*2, 3);
cntEntity * 2, 3);
}
@Test

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

@ -103,7 +103,7 @@ public class HomePageApiTest extends AbstractControllerTest {
for (int i = 0; i < 100; i++) {
Tenant tenant = new Tenant();
tenant.setTitle("tenant" + i);
tenants.add(doPost("/api/tenant", tenant, Tenant.class));
tenants.add(saveTenant(tenant));
}
EntityTypeFilter ef = new EntityTypeFilter();
@ -115,7 +115,7 @@ public class HomePageApiTest extends AbstractControllerTest {
Assert.assertEquals(initialCount + 100, update.getCount());
for (Tenant tenant : tenants) {
doDelete("/api/tenant/" + tenant.getId().toString());
deleteTenant(tenant.getId());
}
}

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

@ -72,7 +72,7 @@ public class OtaPackageControllerTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
savedTenant = saveTenant(tenant);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
@ -94,8 +94,7 @@ public class OtaPackageControllerTest extends AbstractControllerTest {
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant.getId());
}
@Test
@ -339,7 +338,7 @@ public class OtaPackageControllerTest extends AbstractControllerTest {
testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(new OtaPackageInfo(), new OtaPackageInfo(),
savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.ADDED, cntEntity, 0, (cntEntity*2 - startIndexSaveData));
ActionType.ADDED, cntEntity, 0, (cntEntity * 2 - startIndexSaveData));
List<OtaPackageInfo> loadedFirmwares = new ArrayList<>();
PageLink pageLink = new PageLink(24);

5
application/src/test/java/org/thingsboard/server/controller/RpcControllerTest.java

@ -49,7 +49,7 @@ public class RpcControllerTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
savedTenant = saveTenant(tenant);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
@ -66,8 +66,7 @@ public class RpcControllerTest extends AbstractControllerTest {
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant.getId());
}
private Device createDefaultDevice() {

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

@ -82,7 +82,7 @@ public class RuleChainControllerTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
savedTenant = saveTenant(tenant);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
@ -99,8 +99,7 @@ public class RuleChainControllerTest extends AbstractControllerTest {
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant.getId());
}
@Test
@ -147,9 +146,9 @@ public class RuleChainControllerTest extends AbstractControllerTest {
int currentVersion = annotation.version();
String oldConfig = "{\"attrMapping\":{\"serialNumber\":\"sn\"}," +
"\"relationsQuery\":{\"direction\":\"FROM\",\"maxLevel\":1," +
"\"filters\":[{\"relationType\":\"Contains\",\"entityTypes\":[]}]," +
"\"fetchLastLevelOnly\":false},\"telemetry\":false}";
"\"relationsQuery\":{\"direction\":\"FROM\",\"maxLevel\":1," +
"\"filters\":[{\"relationType\":\"Contains\",\"entityTypes\":[]}]," +
"\"fetchLastLevelOnly\":false},\"telemetry\":false}";
TbGetRelatedDataNodeConfiguration defaultConfiguration = new TbGetRelatedDataNodeConfiguration().defaultConfiguration();
String newConfig = JacksonUtil.toString(defaultConfiguration);

5
application/src/test/java/org/thingsboard/server/controller/TbResourceControllerTest.java

@ -73,7 +73,7 @@ public class TbResourceControllerTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
savedTenant = saveTenant(tenant);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
@ -90,8 +90,7 @@ public class TbResourceControllerTest extends AbstractControllerTest {
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant.getId());
}
@Test

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

@ -140,7 +140,7 @@ public class TenantControllerTest extends AbstractControllerTest {
Mockito.reset(tbClusterService);
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Tenant savedTenant = saveTenant(tenant);
Assert.assertNotNull(savedTenant);
Assert.assertNotNull(savedTenant.getId());
Assert.assertTrue(savedTenant.getCreatedTime() > 0);
@ -149,14 +149,13 @@ public class TenantControllerTest extends AbstractControllerTest {
testBroadcastEntityStateChangeEventTimeManyTimeTenant(savedTenant, ComponentLifecycleEvent.CREATED, 1);
savedTenant.setTitle("My new tenant");
doPost("/api/tenant", savedTenant, Tenant.class);
saveTenant(savedTenant);
Tenant foundTenant = doGet("/api/tenant/" + savedTenant.getId().getId().toString(), Tenant.class);
Assert.assertEquals(foundTenant.getTitle(), savedTenant.getTitle());
testBroadcastEntityStateChangeEventTimeManyTimeTenant(savedTenant, ComponentLifecycleEvent.UPDATED, 1);
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant.getId());
testBroadcastEntityStateChangeEventTimeManyTimeTenant(savedTenant, ComponentLifecycleEvent.DELETED, 1);
}
@ -181,12 +180,11 @@ public class TenantControllerTest extends AbstractControllerTest {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Tenant savedTenant = saveTenant(tenant);
Tenant foundTenant = doGet("/api/tenant/" + savedTenant.getId().getId().toString(), Tenant.class);
Assert.assertNotNull(foundTenant);
Assert.assertEquals(savedTenant, foundTenant);
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant.getId());
}
@Test
@ -194,12 +192,11 @@ public class TenantControllerTest extends AbstractControllerTest {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Tenant savedTenant = saveTenant(tenant);
TenantInfo foundTenant = doGet("/api/tenant/info/" + savedTenant.getId().getId().toString(), TenantInfo.class);
Assert.assertNotNull(foundTenant);
Assert.assertEquals(new TenantInfo(savedTenant, "Default"), foundTenant);
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant.getId());
}
@Test
@ -237,11 +234,10 @@ public class TenantControllerTest extends AbstractControllerTest {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Tenant savedTenant = saveTenant(tenant);
String tenantIdStr = savedTenant.getId().getId().toString();
doDelete("/api/tenant/" + tenantIdStr)
.andExpect(status().isOk());
deleteTenant(savedTenant.getId());
doGet("/api/tenant/" + tenantIdStr)
.andExpect(status().isNotFound())
.andExpect(statusReason(containsString(msgErrorNoFound("Tenant", tenantIdStr))));
@ -264,8 +260,7 @@ public class TenantControllerTest extends AbstractControllerTest {
for (int i = 0; i < cntEntity; i++) {
Tenant tenant = new Tenant();
tenant.setTitle("Tenant" + i);
createFutures.add(executor.submit(() ->
doPost("/api/tenant", tenant, Tenant.class)));
createFutures.add(executor.submit(() -> saveTenant(tenant)));
}
tenants.addAll(Futures.allAsList(createFutures).get(TIMEOUT, TimeUnit.SECONDS));
@ -308,8 +303,7 @@ public class TenantControllerTest extends AbstractControllerTest {
String title = title1 + suffix;
title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
tenant.setTitle(title);
createFutures.add(executor.submit(() ->
doPost("/api/tenant", tenant, Tenant.class)));
createFutures.add(executor.submit(() -> saveTenant(tenant)));
}
List<Tenant> tenantsTitle1 = Futures.allAsList(createFutures).get(TIMEOUT, TimeUnit.SECONDS);
@ -323,8 +317,7 @@ public class TenantControllerTest extends AbstractControllerTest {
String title = title2 + suffix;
title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
tenant.setTitle(title);
createFutures.add(executor.submit(() ->
doPost("/api/tenant", tenant, Tenant.class)));
createFutures.add(executor.submit(() -> saveTenant(tenant)));
}
List<Tenant> tenantsTitle2 = Futures.allAsList(createFutures).get(TIMEOUT, TimeUnit.SECONDS);
@ -396,7 +389,7 @@ public class TenantControllerTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("Tenant" + i);
createFutures.add(executor.submit(() ->
new TenantInfo(doPost("/api/tenant", tenant, Tenant.class), "Default")));
new TenantInfo(saveTenant(tenant), "Default")));
}
tenants.addAll(Futures.allAsList(createFutures).get(TIMEOUT, TimeUnit.SECONDS));
@ -448,7 +441,7 @@ public class TenantControllerTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("Isolated tenant");
tenant = doPost("/api/tenant", tenant, Tenant.class);
tenant = saveTenant(tenant);
User tenantUser = new User();
tenantUser.setAuthority(Authority.TENANT_ADMIN);
@ -477,7 +470,7 @@ public class TenantControllerTest extends AbstractControllerTest {
tenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class);
tenant.setTenantProfileId(tenantProfile.getId());
doPost("/api/tenant", tenant, Tenant.class);
saveTenant(tenant);
login(username, password);
@ -507,7 +500,7 @@ public class TenantControllerTest extends AbstractControllerTest {
tenantProfile2 = doPost("/api/tenantProfile", tenantProfile2, TenantProfile.class);
tenant.setTenantProfileId(tenantProfile2.getId());
doPost("/api/tenant", tenant, Tenant.class);
saveTenant(tenant);
login(username, password);
@ -549,7 +542,7 @@ public class TenantControllerTest extends AbstractControllerTest {
loginSysAdmin();
tenant.setTenantProfileId(null);
doPost("/api/tenant", tenant, Tenant.class);
saveTenant(tenant);
login(username, password);
for (Queue queue : foundTenantQueues) {
@ -559,7 +552,7 @@ public class TenantControllerTest extends AbstractControllerTest {
}
loginSysAdmin();
doDelete("/api/tenant/" + tenant.getId().getId().toString()).andExpect(status().isOk());
deleteTenant(tenant.getId());
}
@Test
@ -576,7 +569,7 @@ public class TenantControllerTest extends AbstractControllerTest {
createDifferentTenant();
loginSysAdmin();
savedDifferentTenant.setTenantProfileId(tenantProfile.getId());
savedDifferentTenant = doPost("/api/tenant", savedDifferentTenant, Tenant.class);
savedDifferentTenant = saveTenant(savedDifferentTenant);
TenantId tenantId = differentTenantId;
loginDifferentTenant();
@ -661,7 +654,7 @@ public class TenantControllerTest extends AbstractControllerTest {
createDifferentTenant();
loginSysAdmin();
savedDifferentTenant.setTenantProfileId(tenantProfile.getId());
savedDifferentTenant = doPost("/api/tenant", savedDifferentTenant, Tenant.class);
savedDifferentTenant = saveTenant(savedDifferentTenant);
TenantId tenantId = differentTenantId;
await().atMost(30, TimeUnit.SECONDS)
.until(() -> {
@ -716,7 +709,7 @@ public class TenantControllerTest extends AbstractControllerTest {
createDifferentTenant();
loginSysAdmin();
savedDifferentTenant.setTenantProfileId(tenantProfile.getId());
savedDifferentTenant = doPost("/api/tenant", savedDifferentTenant, Tenant.class);
savedDifferentTenant = saveTenant(savedDifferentTenant);
TenantId tenantId = differentTenantId;
await().atMost(30, TimeUnit.SECONDS).untilAsserted(() -> {
assertThat(partitionService.getMyPartitions(new QueueKey(ServiceType.TB_RULE_ENGINE, tenantId))).isNotNull();

39
application/src/test/java/org/thingsboard/server/controller/TenantProfileControllerTest.java

@ -75,7 +75,7 @@ public class TenantProfileControllerTest extends AbstractControllerTest {
savedTenantProfile.setName("New tenant profile");
doPost("/api/tenantProfile", savedTenantProfile, TenantProfile.class);
TenantProfile foundTenantProfile = doGet("/api/tenantProfile/"+savedTenantProfile.getId().getId().toString(), TenantProfile.class);
TenantProfile foundTenantProfile = doGet("/api/tenantProfile/" + savedTenantProfile.getId().getId().toString(), TenantProfile.class);
Assert.assertEquals(foundTenantProfile.getName(), savedTenantProfile.getName());
testBroadcastEntityStateChangeEventTimeManyTimeTenantProfile(savedTenantProfile, ComponentLifecycleEvent.UPDATED, 1);
@ -100,7 +100,7 @@ public class TenantProfileControllerTest extends AbstractControllerTest {
loginSysAdmin();
TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile");
TenantProfile savedTenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class);
TenantProfile foundTenantProfile = doGet("/api/tenantProfile/"+savedTenantProfile.getId().getId().toString(), TenantProfile.class);
TenantProfile foundTenantProfile = doGet("/api/tenantProfile/" + savedTenantProfile.getId().getId().toString(), TenantProfile.class);
Assert.assertNotNull(foundTenantProfile);
Assert.assertEquals(savedTenantProfile, foundTenantProfile);
}
@ -110,7 +110,7 @@ public class TenantProfileControllerTest extends AbstractControllerTest {
loginSysAdmin();
TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile");
TenantProfile savedTenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class);
EntityInfo foundTenantProfileInfo = doGet("/api/tenantProfileInfo/"+savedTenantProfile.getId().getId().toString(), EntityInfo.class);
EntityInfo foundTenantProfileInfo = doGet("/api/tenantProfileInfo/" + savedTenantProfile.getId().getId().toString(), EntityInfo.class);
Assert.assertNotNull(foundTenantProfileInfo);
Assert.assertEquals(savedTenantProfile.getId(), foundTenantProfileInfo.getId());
Assert.assertEquals(savedTenantProfile.getName(), foundTenantProfileInfo.getName());
@ -129,7 +129,7 @@ public class TenantProfileControllerTest extends AbstractControllerTest {
loginSysAdmin();
TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile 1");
TenantProfile savedTenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class);
TenantProfile defaultTenantProfile = doPost("/api/tenantProfile/"+savedTenantProfile.getId().getId().toString()+"/default", TenantProfile.class);
TenantProfile defaultTenantProfile = doPost("/api/tenantProfile/" + savedTenantProfile.getId().getId().toString() + "/default", TenantProfile.class);
Assert.assertNotNull(defaultTenantProfile);
EntityInfo foundDefaultTenantProfile = doGet("/api/tenantProfileInfo/default", EntityInfo.class);
Assert.assertNotNull(foundDefaultTenantProfile);
@ -176,7 +176,7 @@ public class TenantProfileControllerTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant with tenant profile");
tenant.setTenantProfileId(savedTenantProfile.getId());
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Tenant savedTenant = saveTenant(tenant);
Mockito.reset(tbClusterService);
@ -186,8 +186,7 @@ public class TenantProfileControllerTest extends AbstractControllerTest {
testBroadcastEntityStateChangeEventNeverTenantProfile();
doDelete("/api/tenant/"+savedTenant.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant.getId());
}
@Test
@ -214,7 +213,7 @@ public class TenantProfileControllerTest extends AbstractControllerTest {
List<TenantProfile> tenantProfiles = new ArrayList<>();
PageLink pageLink = new PageLink(17);
PageData<TenantProfile> pageData = doGetTypedWithPageLink("/api/tenantProfiles?",
new TypeReference<>(){}, pageLink);
new TypeReference<>() {}, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(1, pageData.getTotalElements());
tenantProfiles.addAll(pageData.getData());
@ -222,8 +221,8 @@ public class TenantProfileControllerTest extends AbstractControllerTest {
Mockito.reset(tbClusterService);
int cntEntity = 28;
for (int i=0;i<28;i++) {
TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile"+i);
for (int i = 0; i < 28; i++) {
TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile" + i);
tenantProfiles.add(doPost("/api/tenantProfile", tenantProfile, TenantProfile.class));
}
@ -233,7 +232,7 @@ public class TenantProfileControllerTest extends AbstractControllerTest {
pageLink = new PageLink(17);
do {
pageData = doGetTypedWithPageLink("/api/tenantProfiles?",
new TypeReference<>(){}, pageLink);
new TypeReference<>() {}, pageLink);
loadedTenantProfiles.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
@ -256,7 +255,7 @@ public class TenantProfileControllerTest extends AbstractControllerTest {
pageLink = new PageLink(17);
pageData = doGetTypedWithPageLink("/api/tenantProfiles?",
new TypeReference<PageData<TenantProfile>>(){}, pageLink);
new TypeReference<PageData<TenantProfile>>() {}, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(1, pageData.getTotalElements());
@ -269,13 +268,13 @@ public class TenantProfileControllerTest extends AbstractControllerTest {
List<TenantProfile> tenantProfiles = new ArrayList<>();
PageLink pageLink = new PageLink(17);
PageData<TenantProfile> tenantProfilePageData = doGetTypedWithPageLink("/api/tenantProfiles?",
new TypeReference<>(){}, pageLink);
new TypeReference<>() {}, pageLink);
Assert.assertFalse(tenantProfilePageData.hasNext());
Assert.assertEquals(1, tenantProfilePageData.getTotalElements());
tenantProfiles.addAll(tenantProfilePageData.getData());
for (int i=0;i<28;i++) {
TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile"+i);
for (int i = 0; i < 28; i++) {
TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile" + i);
tenantProfiles.add(doPost("/api/tenantProfile", tenantProfile, TenantProfile.class));
}
@ -284,7 +283,7 @@ public class TenantProfileControllerTest extends AbstractControllerTest {
PageData<EntityInfo> pageData;
do {
pageData = doGetTypedWithPageLink("/api/tenantProfileInfos?",
new TypeReference<PageData<EntityInfo>>(){}, pageLink);
new TypeReference<PageData<EntityInfo>>() {}, pageLink);
loadedTenantProfileInfos.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
@ -308,7 +307,7 @@ public class TenantProfileControllerTest extends AbstractControllerTest {
pageLink = new PageLink(17);
pageData = doGetTypedWithPageLink("/api/tenantProfileInfos?",
new TypeReference<PageData<EntityInfo>>(){}, pageLink);
new TypeReference<PageData<EntityInfo>>() {}, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(1, pageData.getTotalElements());
}
@ -354,14 +353,14 @@ public class TenantProfileControllerTest extends AbstractControllerTest {
ArgumentMatcher<TenantProfile> matcherTenantProfile = cntTime == 1 ? argument -> argument.equals(tenantProfile) :
argument -> argument.getClass().equals(TenantProfile.class);
if (ComponentLifecycleEvent.DELETED.equals(event)) {
Mockito.verify(tbClusterService, times( cntTime)).onTenantProfileDelete(Mockito.argThat( matcherTenantProfile),
Mockito.verify(tbClusterService, times(cntTime)).onTenantProfileDelete(Mockito.argThat(matcherTenantProfile),
Mockito.isNull());
testBroadcastEntityStateChangeEventNever(createEntityId_NULL_UUID(new Tenant()));
} else {
Mockito.verify(tbClusterService, times( cntTime)).onTenantProfileChange(Mockito.argThat(matcherTenantProfile),
Mockito.verify(tbClusterService, times(cntTime)).onTenantProfileChange(Mockito.argThat(matcherTenantProfile),
Mockito.isNull());
TenantProfileId tenantProfileIdId = cntTime == 1 ? tenantProfile.getId() : (TenantProfileId) createEntityId_NULL_UUID(tenantProfile);
testBroadcastEntityStateChangeEventTime(tenantProfileIdId, null, cntTime);
testBroadcastEntityStateChangeEventTime(tenantProfileIdId, null, cntTime);
}
Mockito.reset(tbClusterService);
}

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

@ -380,7 +380,7 @@ public class UserControllerTest extends AbstractControllerTest {
//here created a new tenant despite already created on AbstractWebTest and then delete the tenant properly on the last line
Tenant tenant = new Tenant();
tenant.setTitle("My tenant with many admins");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Tenant savedTenant = saveTenant(tenant);
Assert.assertNotNull(savedTenant);
TenantId tenantId = savedTenant.getId();
@ -422,8 +422,7 @@ public class UserControllerTest extends AbstractControllerTest {
assertThat(tenantAdmins).as("admins list size").hasSameSizeAs(loadedTenantAdmins);
assertThat(tenantAdmins).as("admins list content").isEqualTo(loadedTenantAdmins);
doDelete("/api/tenant/" + tenantId.getId().toString())
.andExpect(status().isOk());
deleteTenant(tenantId);
pageLink = new PageLink(33);
pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?",
@ -669,7 +668,7 @@ public class UserControllerTest extends AbstractControllerTest {
List<UserId> expectedCustomerUserIds = new ArrayList<>();
expectedCustomerUserIds.add(customerUserId);
for (int i = 0; i < 45; i++) {
User customerUser = createCustomerUser( customerId);
User customerUser = createCustomerUser(customerId);
customerUser.setEmail(email + StringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)) + "@thingsboard.org");
User user = doPost("/api/user", customerUser, User.class);
expectedCustomerUserIds.add(user.getId());
@ -750,7 +749,7 @@ public class UserControllerTest extends AbstractControllerTest {
String email = "testEmail1";
for (int i = 0; i < 45; i++) {
User customerUser = createCustomerUser( customerId);
User customerUser = createCustomerUser(customerId);
customerUser.setEmail(email + StringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)) + "@thingsboard.org");
doPost("/api/user", customerUser, User.class);
}
@ -993,8 +992,8 @@ public class UserControllerTest extends AbstractControllerTest {
List<UserEmailInfo> usersInfo = getUsersInfo(pageLink);
List<UserEmailInfo> expectedUserInfos = customerUsersContainingWord.stream().map(customerUser -> new UserEmailInfo(customerUser.getId(),
customerUser.getEmail(), customerUser.getFirstName() == null ? "" : customerUser.getFirstName(),
customerUser.getLastName() == null ? "" : customerUser.getLastName()))
customerUser.getEmail(), customerUser.getFirstName() == null ? "" : customerUser.getFirstName(),
customerUser.getLastName() == null ? "" : customerUser.getLastName()))
.sorted(userDataIdComparator).collect(Collectors.toList());
usersInfo.sort(userDataIdComparator);
@ -1046,8 +1045,8 @@ public class UserControllerTest extends AbstractControllerTest {
List<UserEmailInfo> usersInfo = getUsersInfo(pageLink);
List<UserEmailInfo> expectedUserInfos = usersContainingWord.stream().map(customerUser -> new UserEmailInfo(customerUser.getId(),
customerUser.getEmail(), customerUser.getFirstName() == null ? "" : customerUser.getFirstName(),
customerUser.getLastName() == null ? "" : customerUser.getLastName()))
customerUser.getEmail(), customerUser.getFirstName() == null ? "" : customerUser.getFirstName(),
customerUser.getLastName() == null ? "" : customerUser.getLastName()))
.sorted(userDataIdComparator).collect(Collectors.toList());
usersInfo.sort(userDataIdComparator);

24
application/src/test/java/org/thingsboard/server/controller/WidgetTypeControllerTest.java

@ -24,7 +24,6 @@ import org.junit.Test;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.WidgetTypeId;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.widget.WidgetType;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
@ -53,7 +52,7 @@ public class WidgetTypeControllerTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
savedTenant = saveTenant(tenant);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
@ -70,8 +69,7 @@ public class WidgetTypeControllerTest extends AbstractControllerTest {
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/"+savedTenant.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant.getId());
}
@Test
@ -127,10 +125,10 @@ public class WidgetTypeControllerTest extends AbstractControllerTest {
widgetType.setDescriptor(JacksonUtil.fromString("{ \"someKey\": \"someValue\" }", JsonNode.class));
WidgetTypeDetails savedWidgetType = doPost("/api/widgetType", widgetType, WidgetTypeDetails.class);
doDelete("/api/widgetType/"+savedWidgetType.getId().getId().toString())
doDelete("/api/widgetType/" + savedWidgetType.getId().getId().toString())
.andExpect(status().isOk());
doGet("/api/widgetType/"+savedWidgetType.getId().getId().toString())
doGet("/api/widgetType/" + savedWidgetType.getId().getId().toString())
.andExpect(status().isNotFound());
}
@ -173,7 +171,7 @@ public class WidgetTypeControllerTest extends AbstractControllerTest {
widgetsBundle = doPost("/api/widgetsBundle", widgetsBundle, WidgetsBundle.class);
List<WidgetType> widgetTypes = new ArrayList<>();
for (int i=0;i<89;i++) {
for (int i = 0; i < 89; i++) {
WidgetTypeDetails widgetType = new WidgetTypeDetails();
widgetType.setName("Widget Type " + i);
widgetType.setDescriptor(JacksonUtil.fromString("{ \"someKey\": \"someValue\" }", JsonNode.class));
@ -184,7 +182,7 @@ public class WidgetTypeControllerTest extends AbstractControllerTest {
doPost("/api/widgetsBundle/" + widgetsBundle.getId().getId().toString() + "/widgetTypes", widgetTypeIds);
List<WidgetType> loadedWidgetTypes = doGetTyped("/api/widgetTypes?widgetsBundleId={widgetsBundleId}",
new TypeReference<>(){}, widgetsBundle.getId().getId().toString());
new TypeReference<>() {}, widgetsBundle.getId().getId().toString());
Collections.sort(widgetTypes, idComparator);
Collections.sort(loadedWidgetTypes, idComparator);
@ -194,12 +192,12 @@ public class WidgetTypeControllerTest extends AbstractControllerTest {
loginCustomerUser();
List<WidgetType> loadedWidgetTypesCustomer = doGetTyped("/api/widgetTypes?widgetsBundleId={widgetsBundleId}",
new TypeReference<>(){}, widgetsBundle.getId().getId().toString());
new TypeReference<>() {}, widgetsBundle.getId().getId().toString());
Collections.sort(loadedWidgetTypesCustomer, idComparator);
Assert.assertEquals(widgetTypes, loadedWidgetTypesCustomer);
List<WidgetTypeDetails> customerLoadedWidgetTypesDetails = doGetTyped("/api/widgetTypesDetails?widgetsBundleId={widgetsBundleId}",
new TypeReference<>(){}, widgetsBundle.getId().getId().toString());
new TypeReference<>() {}, widgetsBundle.getId().getId().toString());
List<WidgetType> widgetTypesFromDetailsListCustomer = customerLoadedWidgetTypesDetails.stream().map(WidgetType::new).collect(Collectors.toList());
Collections.sort(widgetTypesFromDetailsListCustomer, idComparator);
Assert.assertEquals(widgetTypesFromDetailsListCustomer, loadedWidgetTypes);
@ -207,12 +205,12 @@ public class WidgetTypeControllerTest extends AbstractControllerTest {
loginSysAdmin();
List<WidgetType> sysAdminLoadedWidgetTypes = doGetTyped("/api/widgetTypes?widgetsBundleId={widgetsBundleId}",
new TypeReference<>(){}, widgetsBundle.getId().getId().toString());
new TypeReference<>() {}, widgetsBundle.getId().getId().toString());
Collections.sort(sysAdminLoadedWidgetTypes, idComparator);
Assert.assertEquals(widgetTypes, sysAdminLoadedWidgetTypes);
List<WidgetTypeDetails> sysAdminLoadedWidgetTypesDetails = doGetTyped("/api/widgetTypesDetails?widgetsBundleId={widgetsBundleId}",
new TypeReference<>(){}, widgetsBundle.getId().getId().toString());
new TypeReference<>() {}, widgetsBundle.getId().getId().toString());
List<WidgetType> widgetTypesFromDetailsListSysAdmin = sysAdminLoadedWidgetTypesDetails.stream().map(WidgetType::new).collect(Collectors.toList());
Collections.sort(widgetTypesFromDetailsListSysAdmin, idComparator);
Assert.assertEquals(widgetTypesFromDetailsListSysAdmin, loadedWidgetTypes);
@ -225,7 +223,7 @@ public class WidgetTypeControllerTest extends AbstractControllerTest {
widgetType.setDescriptor(JacksonUtil.fromString("{ \"someKey\": \"someValue\" }", JsonNode.class));
WidgetTypeDetails savedWidgetType = doPost("/api/widgetType", widgetType, WidgetTypeDetails.class);
WidgetType foundWidgetType = doGet("/api/widgetType?fqn={fqn}",
WidgetType.class, "tenant."+savedWidgetType.getFqn());
WidgetType.class, "tenant." + savedWidgetType.getFqn());
Assert.assertNotNull(foundWidgetType);
Assert.assertEquals(new WidgetType(savedWidgetType), foundWidgetType);
}

5
application/src/test/java/org/thingsboard/server/controller/WidgetsBundleControllerTest.java

@ -53,7 +53,7 @@ public class WidgetsBundleControllerTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
savedTenant = saveTenant(tenant);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
@ -70,8 +70,7 @@ public class WidgetsBundleControllerTest extends AbstractControllerTest {
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant.getId());
}
@Test

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

@ -40,7 +40,7 @@ public class TenantEdgeTest extends AbstractEdgeTest {
// updated edge tenant
savedTenant.setTitle("Updated Title for Tenant Edge Test");
edgeImitator.expectMessageAmount(2); // expect tenant and tenant profile update msg
savedTenant = doPost("/api/tenant", savedTenant, Tenant.class);
savedTenant = saveTenant(savedTenant);
Assert.assertTrue(edgeImitator.waitForMessages());
Optional<TenantUpdateMsg> tenantUpdateMsgOpt = edgeImitator.findMessageByType(TenantUpdateMsg.class);
Assert.assertTrue(tenantUpdateMsgOpt.isPresent());
@ -61,7 +61,7 @@ public class TenantEdgeTest extends AbstractEdgeTest {
TenantProfile tenantProfile = createTenantProfile();
savedTenant.setTenantProfileId(tenantProfile.getId());
edgeImitator.expectMessageAmount(2); // expect tenant and tenant profile update msg
savedTenant = doPost("/api/tenant", savedTenant, Tenant.class);
savedTenant = saveTenant(savedTenant);
Assert.assertTrue(edgeImitator.waitForMessages());
tenantUpdateMsgOpt = edgeImitator.findMessageByType(TenantUpdateMsg.class);
Assert.assertTrue(tenantUpdateMsgOpt.isPresent());

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

@ -27,9 +27,9 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.util.ReflectionTestUtils;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.flow.TbRuleChainInputNodeConfiguration;
import org.thingsboard.rule.engine.util.TbMsgSource;
import org.thingsboard.rule.engine.metadata.TbGetAttributesNode;
import org.thingsboard.rule.engine.metadata.TbGetAttributesNodeConfiguration;
import org.thingsboard.rule.engine.util.TbMsgSource;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.DataConstants;
@ -62,7 +62,6 @@ import java.util.List;
import java.util.stream.Collectors;
import static org.mockito.Mockito.spy;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* @author Valerii Sosliuk
@ -103,7 +102,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
savedTenant = saveTenant(tenant);
Assert.assertNotNull(savedTenant);
ruleChainService.deleteRuleChainsByTenantId(savedTenant.getId());
@ -121,7 +120,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
public void afterTest() throws Exception {
loginSysAdmin();
if (savedTenant != null) {
doDelete("/api/tenant/" + savedTenant.getId().getId().toString()).andExpect(status().isOk());
deleteTenant(savedTenant.getId());
}
}

2
application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java

@ -173,7 +173,7 @@ public class DefaultTbAlarmServiceTest {
User user = new User();
user.setEmail("testEmail@gmail.com");
user.setId(new UserId(UUID.randomUUID()));
service.unassignDeletedUserAlarms(new TenantId(UUID.randomUUID()), user, System.currentTimeMillis());
service.unassignDeletedUserAlarms(new TenantId(UUID.randomUUID()), user.getId(), user.getTitle(), System.currentTimeMillis());
ObjectNode commentNode = JacksonUtil.newObjectNode();
commentNode.put("subtype", "ASSIGN");

478
application/src/test/java/org/thingsboard/server/service/housekeeper/HousekeeperServiceTest.java

@ -0,0 +1,478 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.housekeeper;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.TextNode;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentMatcher;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.test.context.TestPropertySource;
import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.metadata.TbGetAttributesNode;
import org.thingsboard.rule.engine.metadata.TbGetAttributesNodeConfiguration;
import org.thingsboard.server.common.data.ApiUsageState;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EventInfo;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.EntityAlarm;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.event.EventType;
import org.thingsboard.server.common.data.event.LifecycleEvent;
import org.thingsboard.server.common.data.housekeeper.HousekeeperTask;
import org.thingsboard.server.common.data.housekeeper.HousekeeperTaskType;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.msg.TbNodeConnectionType;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.controller.AbstractControllerTest;
import org.thingsboard.server.dao.alarm.AlarmDao;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.entity.EntityServiceRegistry;
import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.dao.usagerecord.ApiUsageStateDao;
import org.thingsboard.server.gen.transport.TransportProtos.HousekeeperTaskProto;
import org.thingsboard.server.gen.transport.TransportProtos.ToHousekeeperServiceMsg;
import org.thingsboard.server.service.housekeeper.processor.TsHistoryDeletionTaskProcessor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@DaoSqlTest
@TestPropertySource(properties = {
"transport.http.enabled=true",
"queue.core.housekeeper.task-reprocessing-delay-ms=2000",
"queue.core.housekeeper.poll-interval-ms=1000",
"queue.core.housekeeper.max-reprocessing-attempts=5"
})
public class HousekeeperServiceTest extends AbstractControllerTest {
@SpyBean
private HousekeeperService housekeeperService;
@SpyBean
private HousekeeperReprocessingService housekeeperReprocessingService;
@Autowired
private EventService eventService;
@Autowired
private TimeseriesService timeseriesService;
@Autowired
private AttributesService attributesService;
@Autowired
private RuleChainService ruleChainService;
@Autowired
private AlarmService alarmService;
@Autowired
private AlarmDao alarmDao;
@Autowired
private RelationService relationService;
@Autowired
private ApiUsageStateDao apiUsageStateDao;
@Autowired
private EntityServiceRegistry entityServiceRegistry;
@SpyBean
private TsHistoryDeletionTaskProcessor tsHistoryDeletionTaskProcessor;
private TenantId tenantId;
private static final String TELEMETRY_KEY = "telemetry1";
private static final String ATTRIBUTE_KEY = "_attribute1";
private static final String KV_VALUE = "ewfewfwef";
@Before
public void setUp() throws Exception {
loginTenantAdmin();
this.tenantId = super.tenantId;
}
@After
public void tearDown() throws Exception {
}
@Test
public void whenDeviceIsDeleted_thenCleanUpRelatedData() throws Exception {
Device device = createDevice("test", "test");
createRelatedData(device.getId());
doDelete("/api/device/" + device.getId()).andExpect(status().isOk());
await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> {
verifyNoRelatedData(device.getId());
});
}
@Test
public void whenRuleChainIsDeleted_thenCleanUpRelatedData() throws Exception {
RuleChainMetaData ruleChainMetaData = createRuleChain();
RuleChainId ruleChainId = ruleChainMetaData.getRuleChainId();
RuleNodeId ruleNode1Id = ruleChainMetaData.getNodes().get(0).getId();
RuleNodeId ruleNode2Id = ruleChainMetaData.getNodes().get(1).getId();
createRelatedData(ruleChainId);
createRelatedData(ruleNode1Id);
createRelatedData(ruleNode2Id);
doDelete("/api/ruleChain/" + ruleChainId).andExpect(status().isOk());
await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> {
verifyNoRelatedData(ruleNode1Id);
verifyNoRelatedData(ruleNode2Id);
verifyNoRelatedData(ruleChainId);
});
}
@Test
public void whenUserIsDeleted_thenCleanUpRelatedData() throws Exception {
Device device = createDevice("test", "test");
UserId userId = customerUserId;
createRelatedData(userId);
Alarm alarm = Alarm.builder()
.type("test")
.tenantId(tenantId)
.originator(device.getId())
.severity(AlarmSeverity.MAJOR)
.build();
alarm = doPost("/api/alarm", alarm, Alarm.class);
AlarmId alarmId = alarm.getId();
alarm = doPost("/api/alarm/" + alarmId + "/assign/" + userId, "", Alarm.class);
assertThat(alarm.getAssigneeId()).isEqualTo(userId);
assertThat(alarmService.findAlarmIdsByAssigneeId(tenantId, userId, new PageLink(100)).getData()).isNotEmpty();
doDelete("/api/user/" + userId).andExpect(status().isOk());
await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> {
verifyNoRelatedData(userId);
assertThat(alarmService.findAlarmById(tenantId, alarmId).getAssigneeId()).isNull();
});
}
@Test
public void whenDeviceIsDeleted_thenDeleteAllAlarms() throws Exception {
Device device = createDevice("test", "test");
for (int i = 1; i <= 1000; i++) {
createAlarm(device.getId());
}
doDelete("/api/device/" + device.getId()).andExpect(status().isOk());
await().atMost(30, TimeUnit.SECONDS).untilAsserted(() -> {
verifyNoAlarms(device.getId());
});
}
@Test
public void whenTenantIsDeleted_thenDeleteAllEntitiesAndCleanUpRelatedData() throws Exception {
loginDifferentTenant();
tenantId = differentTenantId;
createRelatedData(tenantId);
createDifferentTenantCustomer();
createRelatedData(differentTenantCustomerId);
loginDifferentTenant();
List<DeviceId> devices = new ArrayList<>();
for (int i = 1; i <= 300; i++) {
Device device = createDevice("test" + i, "test" + i);
devices.add(device.getId());
}
DeviceId firstDevice = devices.get(0);
createRelatedData(firstDevice);
DeviceId lastDevice = devices.get(devices.size() - 1);
createRelatedData(lastDevice);
Asset asset = createAsset();
createRelatedData(asset.getId());
createRelation(firstDevice, asset.getId());
createAlarm(firstDevice, asset.getId());
RuleChainMetaData ruleChainMetaData = createRuleChain();
RuleChainId ruleChainId = ruleChainMetaData.getRuleChainId();
RuleNodeId ruleNode1Id = ruleChainMetaData.getNodes().get(0).getId();
RuleNodeId ruleNode2Id = ruleChainMetaData.getNodes().get(1).getId();
createRelatedData(ruleChainId);
createRelatedData(ruleNode1Id);
createRelatedData(ruleNode2Id);
UserId userId = savedDifferentTenantUser.getId();
createRelatedData(userId);
ApiUsageState tenantApiUsageState = apiUsageStateDao.findApiUsageStateByEntityId(differentTenantId);
loginSysAdmin();
deleteDifferentTenant();
await().atMost(60, TimeUnit.SECONDS).pollInterval(1, TimeUnit.SECONDS).untilAsserted(() -> {
for (DeviceId deviceId : devices) {
verifyNoRelatedData(deviceId);
}
verifyNoRelatedData(asset.getId());
verifyNoRelatedData(ruleNode1Id);
verifyNoRelatedData(ruleNode2Id);
verifyNoRelatedData(ruleChainId);
verifyNoRelatedData(userId);
verifyNoRelatedData(differentTenantCustomerId);
verifyNoRelatedData(tenantApiUsageState.getId());
verifyNoRelatedData(tenantId);
});
}
@Test
public void whenTaskProcessingFails_thenReprocess() throws Exception {
TimeoutException error = new TimeoutException("Test timeout");
doThrow(error).when(tsHistoryDeletionTaskProcessor).process(any());
Device device = createDevice("test", "test");
createRelatedData(device.getId());
doDelete("/api/device/" + device.getId()).andExpect(status().isOk());
int attempts = 2;
await().atMost(30, TimeUnit.SECONDS).pollInterval(1, TimeUnit.SECONDS).untilAsserted(() -> {
for (int i = 0; i <= attempts; i++) {
int attempt = i;
verify(housekeeperReprocessingService).submitForReprocessing(argThat(getTaskMatcher(device.getId(), HousekeeperTaskType.DELETE_TS_HISTORY,
task -> task.getAttempt() == attempt)), argThat(e -> e.getMessage().equals(error.getMessage())));
}
});
assertThat(getTimeseriesHistory(device.getId())).isNotEmpty();
doCallRealMethod().when(tsHistoryDeletionTaskProcessor).process(any());
await().atMost(30, TimeUnit.SECONDS).untilAsserted(() -> {
assertThat(getTimeseriesHistory(device.getId())).isEmpty();
});
}
@Test
public void whenReprocessingAttemptsExceeded_thenDropTheTask() throws Exception {
TimeoutException error = new TimeoutException("Test timeout");
doThrow(error).when(tsHistoryDeletionTaskProcessor).process(any());
Device device = createDevice("test", "test");
createRelatedData(device.getId());
doDelete("/api/device/" + device.getId()).andExpect(status().isOk());
int maxAttempts = 5;
await().atMost(30, TimeUnit.SECONDS).untilAsserted(() -> {
for (int i = 1; i <= maxAttempts; i++) {
verifyTaskProcessing(device.getId(), HousekeeperTaskType.DELETE_TS_HISTORY, i);
}
});
Mockito.clearInvocations(housekeeperService);
doCallRealMethod().when(tsHistoryDeletionTaskProcessor).process(any());
TimeUnit.SECONDS.sleep(2);
verify(housekeeperService, never()).processTask(argThat(getTaskMatcher(device.getId(), HousekeeperTaskType.DELETE_TS_HISTORY, null)));
}
private void verifyTaskProcessing(EntityId entityId, HousekeeperTaskType taskType, int expectedAttempt) throws Exception {
verify(housekeeperService).processTask(argThat(getTaskMatcher(entityId, taskType, task -> task.getAttempt() == expectedAttempt)));
}
private ArgumentMatcher<ToHousekeeperServiceMsg> getTaskMatcher(EntityId entityId, HousekeeperTaskType taskType,
Predicate<HousekeeperTaskProto> additionalCheck) {
return msg -> {
HousekeeperTask task = JacksonUtil.fromString(msg.getTask().getValue(), HousekeeperTask.class);
return task.getEntityId().equals(entityId) && task.getTaskType() == taskType && (additionalCheck == null || additionalCheck.test(msg.getTask()));
};
}
private void createRelatedData(EntityId entityId) throws Exception {
createTelemetry(entityId);
for (AttributeScope scope : AttributeScope.values()) {
createAttribute(entityId, scope, scope + ATTRIBUTE_KEY);
}
createEvent(entityId);
}
private void verifyNoRelatedData(EntityId entityId) throws Exception {
assertThat(entityServiceRegistry.getServiceByEntityType(entityId.getEntityType()).findEntity(tenantId, entityId)).isEmpty();
assertThat(getLatestTelemetry(entityId)).isNull();
assertThat(getTimeseriesHistory(entityId)).isEmpty();
for (AttributeScope scope : AttributeScope.values()) {
assertThat(attributesService.findAll(tenantId, entityId, scope).get()).isEmpty();
}
assertThat(getEvents(entityId)).isEmpty();
assertThat(alarmDao.findEntityAlarmRecordsByEntityId(tenantId, entityId)).isEmpty();
verifyNoAlarms(entityId);
assertThat(relationService.findByTo(tenantId, entityId, RelationTypeGroup.COMMON)).isEmpty();
assertThat(relationService.findByFrom(tenantId, entityId, RelationTypeGroup.COMMON)).isEmpty();
}
private void verifyNoAlarms(EntityId entityId) {
assertThat(alarmService.findAlarmIdsByOriginatorId(tenantId, entityId, 0, null, 10)).isEmpty();
}
private void createAttribute(EntityId entityId, AttributeScope scope, String key) throws Exception {
attributesService.save(tenantId, entityId, scope, new BaseAttributeKvEntry(System.currentTimeMillis(), new StringDataEntry(key, KV_VALUE))).get();
}
private void createTelemetry(EntityId entityId) throws Exception {
timeseriesService.save(tenantId, entityId, new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry(TELEMETRY_KEY, KV_VALUE))).get();
}
private void createEvent(EntityId entityId) {
LifecycleEvent event = LifecycleEvent.builder()
.tenantId(tenantId)
.entityId(entityId.getId())
.serviceId("test")
.lcEventType("test")
.success(true)
.build();
eventService.saveAsync(event);
await().atMost(10, TimeUnit.SECONDS)
.until(() -> !getEvents(entityId).isEmpty());
}
private void createRelation(DeviceId to, AssetId from) {
EntityRelation relation = new EntityRelation(from, to, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON);
relationService.saveRelation(tenantId, relation);
}
private void createAlarm(DeviceId deviceId, EntityId propagatedEntityId) {
Alarm alarm = doPost("/api/alarm", Alarm.builder()
.tenantId(tenantId)
.originator(deviceId)
.severity(AlarmSeverity.CRITICAL)
.type("test alarm for " + deviceId)
.propagate(true)
.build(), Alarm.class);
List<EntityAlarm> entityAlarms = alarmDao.findEntityAlarmRecords(tenantId, alarm.getId());
assertThat(entityAlarms).anyMatch(entityAlarm -> entityAlarm.getEntityId().equals(deviceId) && entityAlarm.getAlarmType().equals(alarm.getType()));
assertThat(entityAlarms).anyMatch(entityAlarm -> entityAlarm.getEntityId().equals(propagatedEntityId) && entityAlarm.getAlarmType().equals(alarm.getType()));
assertThat(alarmService.findAlarmIdsByOriginatorId(tenantId, deviceId, 0, null, 10)).isNotEmpty();
}
private void createAlarm(DeviceId deviceId) {
Alarm alarm = doPost("/api/alarm", Alarm.builder()
.tenantId(tenantId)
.originator(deviceId)
.severity(AlarmSeverity.CRITICAL)
.type("test alarm for " + deviceId + " " + RandomStringUtils.randomAlphabetic(10))
.build(), Alarm.class);
assertThat(alarmService.findAlarmIdsByOriginatorId(tenantId, deviceId, 0, null, 10)).isNotEmpty();
}
private TsKvEntry getLatestTelemetry(EntityId entityId) throws Exception {
return timeseriesService.findLatest(tenantId, entityId, HousekeeperServiceTest.TELEMETRY_KEY).get().orElse(null);
}
private List<TsKvEntry> getTimeseriesHistory(EntityId entityId) throws Exception {
return timeseriesService.findAll(tenantId, entityId, List.of(new BaseReadTsKvQuery(HousekeeperServiceTest.TELEMETRY_KEY, 0, System.currentTimeMillis(), 10, "DESC"))).get();
}
private List<EventInfo> getEvents(EntityId entityId) {
return eventService.findEvents(tenantId, entityId, EventType.LC_EVENT, new TimePageLink(100)).getData()
.stream().filter(event -> Optional.ofNullable(event.getBody()).map(body -> body.get("event"))
.map(JsonNode::asText).orElse("").equals("test"))
.collect(Collectors.toList());
}
private Asset createAsset() {
Asset asset = new Asset();
asset.setName("test");
asset.setType("test");
return doPost("/api/asset", asset, Asset.class);
}
private RuleChainMetaData createRuleChain() {
RuleChain ruleChain = new RuleChain();
ruleChain.setTenantId(tenantId);
ruleChain.setName("Test");
ruleChain.setType(RuleChainType.CORE);
ruleChain.setDebugMode(true);
ruleChain.setConfiguration(JacksonUtil.newObjectNode().set("a", new TextNode("b")));
ruleChain = ruleChainService.saveRuleChain(ruleChain);
RuleChainId ruleChainId = ruleChain.getId();
RuleChainMetaData metaData = new RuleChainMetaData();
metaData.setRuleChainId(ruleChainId);
RuleNode ruleNode1 = new RuleNode();
ruleNode1.setName("Simple Rule Node 1");
ruleNode1.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName());
ruleNode1.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version());
ruleNode1.setDebugMode(true);
TbGetAttributesNodeConfiguration configuration1 = new TbGetAttributesNodeConfiguration();
configuration1.setServerAttributeNames(Collections.singletonList("serverAttributeKey1"));
ruleNode1.setConfiguration(JacksonUtil.valueToTree(configuration1));
RuleNode ruleNode2 = new RuleNode();
ruleNode2.setName("Simple Rule Node 2");
ruleNode2.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName());
ruleNode2.setConfigurationVersion(TbGetAttributesNode.class.getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class).version());
ruleNode2.setDebugMode(true);
TbGetAttributesNodeConfiguration configuration2 = new TbGetAttributesNodeConfiguration();
configuration2.setServerAttributeNames(Collections.singletonList("serverAttributeKey2"));
ruleNode2.setConfiguration(JacksonUtil.valueToTree(configuration2));
metaData.setNodes(Arrays.asList(ruleNode1, ruleNode2));
metaData.setFirstNodeIndex(0);
metaData.addConnectionInfo(0, 1, TbNodeConnectionType.SUCCESS);
ruleChainService.saveRuleChainMetaData(tenantId, metaData, Function.identity());
return ruleChainService.loadRuleChainMetaData(tenantId, ruleChainId);
}
}

23
application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java

@ -323,8 +323,10 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
deleteDifferentTenant();
assertThat(notificationRequestService.findNotificationRequestsByTenantIdAndOriginatorType(tenantId, EntityType.USER, new PageLink(1)).getTotalElements())
.isZero();
await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> {
assertThat(notificationRequestService.findNotificationRequestsByTenantIdAndOriginatorType(tenantId, EntityType.USER, new PageLink(1)).getTotalElements())
.isZero();
});
}
@Test
@ -523,18 +525,17 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
@Test
public void testNotificationRequestStats() throws Exception {
wsClient.subscribeForUnreadNotifications(10);
wsClient.waitForReply(true);
wsClient.registerWaitForUpdate();
NotificationTarget notificationTarget = createNotificationTarget(customerUserId);
NotificationRequest notificationRequest = submitNotificationRequest(notificationTarget.getId(), "Test :)", NotificationDeliveryMethod.WEB);
wsClient.waitForUpdate();
await().atMost(2, TimeUnit.SECONDS)
.until(() -> findNotificationRequest(notificationRequest.getId()).isSent());
NotificationRequestStats stats = getStats(notificationRequest.getId());
NotificationRequest notificationRequest = submitNotificationRequest(notificationTarget.getId(), "Test :)", NotificationDeliveryMethod.WEB);
NotificationRequestStats stats = awaitNotificationRequest(notificationRequest.getId());
assertThat(stats.getSent().get(NotificationDeliveryMethod.WEB)).hasValue(1);
doDelete("/api/user/mobile/session").andExpect(status().isOk());
notificationRequest = submitNotificationRequest(notificationTarget.getId(), "Test", NotificationDeliveryMethod.MOBILE_APP);
stats = awaitNotificationRequest(notificationRequest.getId());
assertThat(stats.getErrors().get(NotificationDeliveryMethod.MOBILE_APP)).hasSize(1);
assertThat(stats.getTotalErrors()).hasValue(1);
}
@Test

7
application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java

@ -106,8 +106,11 @@ public class NotificationApiWsClient extends TbTestWebSocketClient {
}
}
} else if (updateType == CmdUpdateType.NOTIFICATIONS_COUNT) {
lastCountUpdate = JacksonUtil.treeToValue(update, UnreadNotificationsCountUpdate.class);
unreadCount = lastCountUpdate.getTotalUnreadCount();
UnreadNotificationsCountUpdate countUpdate = JacksonUtil.treeToValue(update, UnreadNotificationsCountUpdate.class);
if (lastCountUpdate == null || countUpdate.getSequenceNumber() > lastCountUpdate.getSequenceNumber()) {
lastCountUpdate = countUpdate;
unreadCount = lastCountUpdate.getTotalUnreadCount();
}
}
super.onMessage(s);
}

6
application/src/test/java/org/thingsboard/server/service/notification/NotificationTargetApiTest.java

@ -38,8 +38,10 @@ import org.thingsboard.server.dao.service.DaoSqlTest;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@DaoSqlTest
@ -148,7 +150,9 @@ public class NotificationTargetApiTest extends AbstractNotificationApiTest {
assertThat(notificationTargetDao.findByTenantIdAndSupportedNotificationTypeAndPageLink(differentTenantId, NotificationType.GENERAL, new PageLink(10)).getData()).isNotEmpty();
deleteDifferentTenant();
assertThat(notificationTargetDao.findByTenantIdAndPageLink(differentTenantId, new PageLink(10)).getData()).isEmpty();
await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> {
assertThat(notificationTargetDao.findByTenantIdAndPageLink(differentTenantId, new PageLink(10)).getData()).isEmpty();
});
}
@Test

16
application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java

@ -51,7 +51,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@DaoSqlTest
public class BaseTbResourceServiceTest extends AbstractControllerTest {
@ -129,7 +128,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
savedTenant = saveTenant(tenant);
tenantId = savedTenant.getId();
Assert.assertNotNull(savedTenant);
@ -147,8 +146,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
deleteTenant(savedTenant.getId());
}
@Test
@ -397,7 +395,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("Test tenant");
tenant = doPost("/api/tenant", tenant, Tenant.class);
tenant = saveTenant(tenant);
TenantId tenantId = tenant.getId();
@ -441,8 +439,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
Assert.assertFalse(pageData.hasNext());
Assert.assertTrue(pageData.getData().isEmpty());
doDelete("/api/tenant/" + tenantId.getId().toString())
.andExpect(status().isOk());
deleteTenant(tenantId);
}
@Test
@ -451,7 +448,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("Test tenant");
tenant = doPost("/api/tenant", tenant, Tenant.class);
tenant = saveTenant(tenant);
TenantId tenantId = tenant.getId();
@ -518,8 +515,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
Assert.assertFalse(pageData.hasNext());
Assert.assertTrue(pageData.getData().isEmpty());
doDelete("/api/tenant/" + tenantId.getId().toString())
.andExpect(status().isOk());
deleteTenant(tenantId);
}
}

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

@ -50,8 +50,6 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@DaoSqlTest
public class SequentialTimeseriesPersistenceTest extends AbstractControllerTest {
@ -77,7 +75,7 @@ public class SequentialTimeseriesPersistenceTest extends AbstractControllerTest
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
savedTenant = saveTenant(tenant);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
@ -93,7 +91,7 @@ public class SequentialTimeseriesPersistenceTest extends AbstractControllerTest
@After
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString()).andExpect(status().isOk());
deleteTenant(savedTenant.getId());
}
@Test
@ -147,7 +145,7 @@ public class SequentialTimeseriesPersistenceTest extends AbstractControllerTest
void checkDiffBetweenLatestTsForDevicesAndAsset(List<Device> devices, Asset asset) throws ExecutionException, InterruptedException, TimeoutException {
TsKvEntry assetTsKvEntry = getTsKvLatest(asset.getId(), GENERIC_CUMULATIVE_OBJ);
Assert.assertTrue(assetTsKvEntry.getJsonValue().isPresent());
JsonObject assetJsonObject = new JsonParser().parse(assetTsKvEntry.getJsonValue().get()).getAsJsonObject();
JsonObject assetJsonObject = JsonParser.parseString(assetTsKvEntry.getJsonValue().get()).getAsJsonObject();
for (Device device : devices) {
Long assetValue = assetJsonObject.get(device.getName()).getAsLong();
TsKvEntry deviceLatest = getTsKvLatest(device.getId(), TOTALIZER);
@ -182,7 +180,7 @@ public class SequentialTimeseriesPersistenceTest extends AbstractControllerTest
JsonObject getJsonObject(String key, long value, Optional<String> tsKvEntryOpt) {
JsonObject jsonObject = new JsonObject();
if (tsKvEntryOpt.isPresent()) {
jsonObject = new JsonParser().parse(tsKvEntryOpt.get()).getAsJsonObject();
jsonObject = JsonParser.parseString(tsKvEntryOpt.get()).getAsJsonObject();
}
jsonObject.addProperty(key, value);
return jsonObject;

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

Loading…
Cancel
Save