Browse Source

Merge with master

pull/5520/head
Andrii Shvaika 5 years ago
parent
commit
942c316e8a
  1. 19
      application/pom.xml
  2. 2
      application/src/main/data/json/system/widget_bundles/cards.json
  3. 10
      application/src/main/data/json/system/widget_bundles/maps.json
  4. 1
      application/src/main/java/org/thingsboard/server/ThingsboardInstallApplication.java
  5. 2
      application/src/main/java/org/thingsboard/server/ThingsboardServerApplication.java
  6. 320
      application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java
  7. 1
      application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java
  8. 13
      application/src/main/java/org/thingsboard/server/config/WebConfig.java
  9. 2
      application/src/main/java/org/thingsboard/server/controller/AdminController.java
  10. 4
      application/src/main/java/org/thingsboard/server/controller/AssetController.java
  11. 72
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  12. 6
      application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java
  13. 2
      application/src/main/java/org/thingsboard/server/controller/CustomerController.java
  14. 30
      application/src/main/java/org/thingsboard/server/controller/DashboardController.java
  15. 4
      application/src/main/java/org/thingsboard/server/controller/DeviceController.java
  16. 16
      application/src/main/java/org/thingsboard/server/controller/EdgeController.java
  17. 4
      application/src/main/java/org/thingsboard/server/controller/EntityViewController.java
  18. 41
      application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
  19. 140
      application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
  20. 4
      application/src/main/java/org/thingsboard/server/exception/ThingsboardCredentialsExpiredResponse.java
  21. 20
      application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponse.java
  22. 84
      application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java
  23. 9
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  24. 73
      application/src/main/java/org/thingsboard/server/service/asset/AssetBulkImportService.java
  25. 109
      application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java
  26. 81
      application/src/main/java/org/thingsboard/server/service/edge/EdgeBulkImportService.java
  27. 67
      application/src/main/java/org/thingsboard/server/service/importing/AbstractBulkImportService.java
  28. 18
      application/src/main/java/org/thingsboard/server/service/install/update/CacheCleanupService.java
  29. 75
      application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java
  30. 65
      application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java
  31. 1
      application/src/main/java/org/thingsboard/server/service/install/update/PaginatedUpdater.java
  32. 11
      application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServiceImpl.java
  33. 15
      application/src/main/java/org/thingsboard/server/service/queue/processing/TbRuleEngineProcessingStrategyFactory.java
  34. 5
      application/src/main/java/org/thingsboard/server/service/security/auth/jwt/extractor/JwtHeaderTokenExtractor.java
  35. 7
      application/src/main/java/org/thingsboard/server/service/security/auth/rest/LoginRequest.java
  36. 34
      application/src/main/java/org/thingsboard/server/service/security/auth/rest/LoginResponse.java
  37. 2
      application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java
  38. 209
      application/src/main/resources/thingsboard.yml
  39. 14
      application/src/test/java/org/thingsboard/server/controller/BaseAssetControllerTest.java
  40. 22
      application/src/test/java/org/thingsboard/server/controller/BaseCustomerControllerTest.java
  41. 7
      application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java
  42. 14
      application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java
  43. 7
      application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java
  44. 13
      application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java
  45. 8
      application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java
  46. 20
      application/src/test/java/org/thingsboard/server/controller/BaseOtaPackageControllerTest.java
  47. 9
      application/src/test/java/org/thingsboard/server/controller/BaseRuleChainControllerTest.java
  48. 12
      application/src/test/java/org/thingsboard/server/controller/BaseTbResourceControllerTest.java
  49. 8
      application/src/test/java/org/thingsboard/server/controller/BaseTenantControllerTest.java
  50. 8
      application/src/test/java/org/thingsboard/server/controller/BaseTenantProfileControllerTest.java
  51. 22
      application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java
  52. 8
      application/src/test/java/org/thingsboard/server/controller/BaseWidgetsBundleControllerTest.java
  53. 97
      application/src/test/java/org/thingsboard/server/service/install/update/DefaultDataUpdateServiceTest.java
  54. 21
      application/src/test/java/org/thingsboard/server/transport/mqtt/AbstractMqttIntegrationTest.java
  55. 588
      application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/AbstractMqttAttributesIntegrationTest.java
  56. 94
      application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/AbstractMqttAttributesRequestBackwardCompatibilityIntegrationTest.java
  57. 110
      application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java
  58. 11
      application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/AbstractMqttAttributesRequestJsonIntegrationTest.java
  59. 213
      application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java
  60. 23
      application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/sql/MqttAttributesRequestBackwardCompatibilityIntegrationTest.java
  61. 2
      application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/sql/MqttAttributesRequestIntegrationTest.java
  62. 2
      application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/sql/MqttAttributesRequestJsonIntegrationTest.java
  63. 2
      application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/sql/MqttAttributesRequestProtoIntegrationTest.java
  64. 69
      application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/AbstractMqttAttributesUpdatesBackwardCompatibilityIntegrationTest.java
  65. 132
      application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/AbstractMqttAttributesUpdatesIntegrationTest.java
  66. 20
      application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/AbstractMqttAttributesUpdatesJsonIntegrationTest.java
  67. 113
      application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java
  68. 23
      application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/sql/MqttAttributesUpdatesBackwardCompatibilityIntegrationTest.java
  69. 2
      application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/sql/MqttAttributesUpdatesIntegrationTest.java
  70. 2
      application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/sql/MqttAttributesUpdatesJsonIntegrationTest.java
  71. 2
      application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/sql/MqttAttributesUpdatesProtoIntegrationTest.java
  72. 50
      application/src/test/java/org/thingsboard/server/transport/mqtt/claim/AbstractMqttClaimBackwardCompatibilityDeviceTest.java
  73. 33
      application/src/test/java/org/thingsboard/server/transport/mqtt/claim/AbstractMqttClaimDeviceTest.java
  74. 30
      application/src/test/java/org/thingsboard/server/transport/mqtt/claim/AbstractMqttClaimProtoDeviceTest.java
  75. 24
      application/src/test/java/org/thingsboard/server/transport/mqtt/claim/sql/MqttClaimDeviceBackwardCompatibilityTest.java
  76. 2
      application/src/test/java/org/thingsboard/server/transport/mqtt/claim/sql/MqttClaimDeviceJsonTest.java
  77. 2
      application/src/test/java/org/thingsboard/server/transport/mqtt/claim/sql/MqttClaimDeviceProtoTest.java
  78. 2
      application/src/test/java/org/thingsboard/server/transport/mqtt/claim/sql/MqttClaimDeviceTest.java
  79. 14
      application/src/test/java/org/thingsboard/server/transport/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java
  80. 14
      application/src/test/java/org/thingsboard/server/transport/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java
  81. 2
      application/src/test/java/org/thingsboard/server/transport/mqtt/provision/sql/MqttProvisionDeviceJsonTest.java
  82. 2
      application/src/test/java/org/thingsboard/server/transport/mqtt/provision/sql/MqttProvisionDeviceProtoTest.java
  83. 94
      application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcBackwardCompatibilityIntegrationTest.java
  84. 10
      application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcDefaultIntegrationTest.java
  85. 246
      application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java
  86. 12
      application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcJsonIntegrationTest.java
  87. 147
      application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java
  88. 24
      application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/sql/MqttServerSideRpcBackwardCompatibilityIntegrationTest.java
  89. 2
      application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/sql/MqttServerSideRpcIntegrationTest.java
  90. 2
      application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/sql/MqttServerSideRpcJsonIntegrationTest.java
  91. 2
      application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/sql/MqttServerSideRpcProtoIntegrationTest.java
  92. 6
      application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java
  93. 2
      application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/sql/MqttAttributesIntegrationTest.java
  94. 2
      application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/sql/MqttAttributesJsonIntegrationTest.java
  95. 2
      application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/sql/MqttAttributesProtoIntegrationTest.java
  96. 17
      application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java
  97. 25
      application/src/test/resources/application-test.properties
  98. 3
      application/src/test/resources/update/330/README.md
  99. 173
      application/src/test/resources/update/330/device_profile_001_in.json
  100. 189
      application/src/test/resources/update/330/device_profile_001_out.json

19
application/pom.xml

@ -250,8 +250,8 @@
<artifactId>grpc-stub</artifactId> <artifactId>grpc-stub</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.springfox</groupId> <groupId>org.thingsboard</groupId>
<artifactId>springfox-swagger2</artifactId> <artifactId>springfox-boot-starter</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.sun.winsw</groupId> <groupId>com.sun.winsw</groupId>
@ -319,10 +319,6 @@
<groupId>org.javadelight</groupId> <groupId>org.javadelight</groupId>
<artifactId>delight-nashorn-sandbox</artifactId> <artifactId>delight-nashorn-sandbox</artifactId>
</dependency> </dependency>
<dependency>
<groupId>io.springfox.ui</groupId>
<artifactId>springfox-swagger-ui-rfc6570</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.passay</groupId> <groupId>org.passay</groupId>
<artifactId>passay</artifactId> <artifactId>passay</artifactId>
@ -343,6 +339,17 @@
<resources> <resources>
<resource> <resource>
<directory>${project.basedir}/src/main/resources</directory> <directory>${project.basedir}/src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>thingsboard.yml</include>
</includes>
</resource>
<resource>
<directory>${project.basedir}/src/main/resources</directory>
<filtering>false</filtering>
<excludes>
<exclude>thingsboard.yml</exclude>
</excludes>
</resource> </resource>
</resources> </resources>
<plugins> <plugins>

2
application/src/main/data/json/system/widget_bundles/cards.json

@ -55,7 +55,7 @@
"templateHtml": "<tb-timeseries-table-widget \n [ctx]=\"ctx\">\n</tb-timeseries-table-widget>", "templateHtml": "<tb-timeseries-table-widget \n [ctx]=\"ctx\">\n</tb-timeseries-table-widget>",
"templateCss": "", "templateCss": "",
"controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeseriesTableWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n ignoreDataUpdateOnIntervalTick: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true,\n hasShowCondition: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}", "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeseriesTableWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n ignoreDataUpdateOnIntervalTick: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true,\n hasShowCondition: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}",
"settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"TimeseriesTableSettings\",\n \"properties\": {\n \"enableSearch\": {\n \"title\": \"Enable search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyHeader\": {\n \"title\": \"Always display header\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyAction\": {\n \"title\": \"Always display actions column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"reserveSpaceForHiddenAction\": {\n \"title\": \"Hidden cell button actions display mode\",\n \"type\": \"string\",\n \"default\": \"true\"\n },\n \"showTimestamp\": {\n \"title\": \"Display timestamp column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"showMilliseconds\": {\n \"title\": \"Display timestamp milliseconds\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n }, \n \"useEntityLabel\": {\n \"title\": \"Use entity label in tab name\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"hideEmptyLines\": {\n \"title\": \"Hide empty lines\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"disableStickyHeader\": {\n \"title\": \"Disable sticky header\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"useRowStyleFunction\": {\n \"title\": \"Use row style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"rowStyleFunction\": {\n \"title\": \"Row style function: f(rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"enableSearch\",\n \"enableStickyHeader\",\n \"enableStickyAction\",\n {\n \"key\": \"reserveSpaceForHiddenAction\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"true\",\n \"label\": \"Show empty space instead of hidden cell button action\"\n },\n {\n \"value\": \"false\",\n \"label\": \"Don't reserve space for hidden action buttons\"\n }\n ]\n },\n \"showTimestamp\",\n \"showMilliseconds\",\n \"displayPagination\",\n \"useEntityLabel\",\n \"defaultPageSize\",\n \"identifyDeviceSelector\",\n \"hideEmptyLines\",\n \"useRowStyleFunction\",\n {\n \"key\": \"rowStyleFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/timeseries/row_style_fn\",\n \"condition\": \"model.useRowStyleFunction === true\"\n }\n ]\n}", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"TimeseriesTableSettings\",\n \"properties\": {\n \"enableSearch\": {\n \"title\": \"Enable search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyHeader\": {\n \"title\": \"Always display header\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyAction\": {\n \"title\": \"Always display actions column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"reserveSpaceForHiddenAction\": {\n \"title\": \"Hidden cell button actions display mode\",\n \"type\": \"string\",\n \"default\": \"true\"\n },\n \"showTimestamp\": {\n \"title\": \"Display timestamp column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"showMilliseconds\": {\n \"title\": \"Display timestamp milliseconds\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n }, \n \"useEntityLabel\": {\n \"title\": \"Use entity label in tab name\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"hideEmptyLines\": {\n \"title\": \"Hide empty lines\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"disableStickyHeader\": {\n \"title\": \"Disable sticky header\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"useRowStyleFunction\": {\n \"title\": \"Use row style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"rowStyleFunction\": {\n \"title\": \"Row style function: f(rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"enableSearch\",\n \"enableStickyHeader\",\n \"enableStickyAction\",\n {\n \"key\": \"reserveSpaceForHiddenAction\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"true\",\n \"label\": \"Show empty space instead of hidden cell button action\"\n },\n {\n \"value\": \"false\",\n \"label\": \"Don't reserve space for hidden action buttons\"\n }\n ]\n },\n \"showTimestamp\",\n \"showMilliseconds\",\n \"displayPagination\",\n \"useEntityLabel\",\n \"defaultPageSize\",\n \"hideEmptyLines\",\n \"useRowStyleFunction\",\n {\n \"key\": \"rowStyleFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/timeseries/row_style_fn\",\n \"condition\": \"model.useRowStyleFunction === true\"\n }\n ]\n}",
"dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value, rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/timeseries/cell_style_fn\",\n \"condition\": \"model.useCellStyleFunction === true\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/timeseries/cell_content_fn\",\n \"condition\": \"model.useCellContentFunction === true\"\n }\n ]\n}", "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value, rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/timeseries/cell_style_fn\",\n \"condition\": \"model.useCellStyleFunction === true\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/timeseries/cell_content_fn\",\n \"condition\": \"model.useCellContentFunction === true\"\n }\n ]\n}",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature °C\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = (value + 60)/120 * 100;\\n var color = tinycolor.mix('blue', 'red', amount = percent);\\n color.setAlpha(.5);\\n return {\\n paddingLeft: '20px',\\n color: '#ffffff',\\n background: color.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\"},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity, %\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = value;\\n var backgroundColor = tinycolor('blue');\\n backgroundColor.setAlpha(value/100);\\n var color = 'blue';\\n if (value > 50) {\\n color = 'white';\\n }\\n \\n return {\\n paddingLeft: '20px',\\n color: color,\\n background: backgroundColor.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 5) {\\n\\tvalue = 5;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":60000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"showTimestamp\":true,\"displayPagination\":true,\"defaultPageSize\":10},\"title\":\"Timeseries table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\"}" "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature °C\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = (value + 60)/120 * 100;\\n var color = tinycolor.mix('blue', 'red', amount = percent);\\n color.setAlpha(.5);\\n return {\\n paddingLeft: '20px',\\n color: '#ffffff',\\n background: color.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\"},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity, %\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = value;\\n var backgroundColor = tinycolor('blue');\\n backgroundColor.setAlpha(value/100);\\n var color = 'blue';\\n if (value > 50) {\\n color = 'white';\\n }\\n \\n return {\\n paddingLeft: '20px',\\n color: color,\\n background: backgroundColor.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 5) {\\n\\tvalue = 5;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":60000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"showTimestamp\":true,\"displayPagination\":true,\"defaultPageSize\":10},\"title\":\"Timeseries table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\"}"
} }

10
application/src/main/data/json/system/widget_bundles/maps.json

File diff suppressed because one or more lines are too long

1
application/src/main/java/org/thingsboard/server/ThingsboardInstallApplication.java

@ -31,6 +31,7 @@ import java.util.Arrays;
"org.thingsboard.server.service.install", "org.thingsboard.server.service.install",
"org.thingsboard.server.dao", "org.thingsboard.server.dao",
"org.thingsboard.server.common.stats", "org.thingsboard.server.common.stats",
"org.thingsboard.server.common.transport.config.ssl",
"org.thingsboard.server.cache"}) "org.thingsboard.server.cache"})
public class ThingsboardInstallApplication { public class ThingsboardInstallApplication {

2
application/src/main/java/org/thingsboard/server/ThingsboardServerApplication.java

@ -20,13 +20,11 @@ import org.springframework.boot.SpringBootConfiguration;
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.EnableScheduling;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.Arrays; import java.util.Arrays;
@SpringBootConfiguration @SpringBootConfiguration
@EnableAsync @EnableAsync
@EnableSwagger2
@EnableScheduling @EnableScheduling
@ComponentScan({"org.thingsboard.server"}) @ComponentScan({"org.thingsboard.server"})
public class ThingsboardServerApplication { public class ThingsboardServerApplication {

320
application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java

@ -15,33 +15,75 @@
*/ */
package org.thingsboard.server.config; package org.thingsboard.server.config;
import com.fasterxml.classmate.ResolvedType;
import com.fasterxml.classmate.TypeResolver; import com.fasterxml.classmate.TypeResolver;
import com.fasterxml.jackson.databind.JsonNode; import lombok.extern.slf4j.Slf4j;
import com.google.common.base.Predicate; import org.apache.commons.lang3.RandomStringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.exception.ThingsboardCredentialsExpiredResponse;
import org.thingsboard.server.exception.ThingsboardErrorResponse;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.auth.rest.LoginRequest;
import org.thingsboard.server.service.security.auth.rest.LoginResponse;
import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.schema.AlternateTypeRule; import springfox.documentation.builders.ExampleBuilder;
import springfox.documentation.builders.OperationBuilder;
import springfox.documentation.builders.RepresentationBuilder;
import springfox.documentation.builders.RequestParameterBuilder;
import springfox.documentation.builders.ResponseBuilder;
import springfox.documentation.schema.Example;
import springfox.documentation.service.ApiDescription;
import springfox.documentation.service.ApiInfo; import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ApiKey; import springfox.documentation.service.ApiListing;
import springfox.documentation.service.AuthorizationScope; import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.Contact; import springfox.documentation.service.Contact;
import springfox.documentation.service.HttpLoginPasswordScheme;
import springfox.documentation.service.ParameterType;
import springfox.documentation.service.Response;
import springfox.documentation.service.SecurityReference; import springfox.documentation.service.SecurityReference;
import springfox.documentation.service.SecurityScheme;
import springfox.documentation.service.Tag;
import springfox.documentation.spi.DocumentationType; import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.ApiListingBuilderPlugin;
import springfox.documentation.spi.service.ApiListingScannerPlugin;
import springfox.documentation.spi.service.contexts.ApiListingContext;
import springfox.documentation.spi.service.contexts.DocumentationContext;
import springfox.documentation.spi.service.contexts.OperationContext;
import springfox.documentation.spi.service.contexts.SecurityContext; import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.spring.web.readers.operation.CachingOperationNameGenerator;
import springfox.documentation.swagger.common.SwaggerPluginSupport;
import springfox.documentation.swagger.web.DocExpansion;
import springfox.documentation.swagger.web.ModelRendering;
import springfox.documentation.swagger.web.OperationsSorter;
import springfox.documentation.swagger.web.UiConfiguration;
import springfox.documentation.swagger.web.UiConfigurationBuilder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
import static com.google.common.base.Predicates.and;
import static com.google.common.base.Predicates.not;
import static com.google.common.collect.Lists.newArrayList; import static com.google.common.collect.Lists.newArrayList;
import static java.util.function.Predicate.not;
import static springfox.documentation.builders.PathSelectors.any;
import static springfox.documentation.builders.PathSelectors.regex; import static springfox.documentation.builders.PathSelectors.regex;
@Slf4j
@Configuration @Configuration
@TbCoreComponent
public class SwaggerConfiguration { public class SwaggerConfiguration {
@Value("${swagger.api_path_regex}") @Value("${swagger.api_path_regex}")
@ -66,40 +108,113 @@ public class SwaggerConfiguration {
private String licenseUrl; private String licenseUrl;
@Value("${swagger.version}") @Value("${swagger.version}")
private String version; private String version;
@Value("${app.version:unknown}")
private String appVersion;
@Bean @Bean
public Docket thingsboardApi() { public Docket thingsboardApi() {
TypeResolver typeResolver = new TypeResolver(); TypeResolver typeResolver = new TypeResolver();
final ResolvedType jsonNodeType = return new Docket(DocumentationType.OAS_30)
typeResolver.resolve(
JsonNode.class);
final ResolvedType stringType =
typeResolver.resolve(
String.class);
return new Docket(DocumentationType.SWAGGER_2)
.groupName("thingsboard") .groupName("thingsboard")
.apiInfo(apiInfo()) .apiInfo(apiInfo())
.alternateTypeRules( .additionalModels(
new AlternateTypeRule( typeResolver.resolve(ThingsboardErrorResponse.class),
jsonNodeType, typeResolver.resolve(ThingsboardCredentialsExpiredResponse.class),
stringType)) typeResolver.resolve(LoginRequest.class),
typeResolver.resolve(LoginResponse.class)
)
.select() .select()
.paths(apiPaths()) .paths(apiPaths())
.paths(any())
.build() .build()
.securitySchemes(newArrayList(jwtTokenKey())) .globalResponses(HttpMethod.GET,
defaultErrorResponses(false)
)
.globalResponses(HttpMethod.POST,
defaultErrorResponses(true)
)
.globalResponses(HttpMethod.DELETE,
defaultErrorResponses(false)
)
.securitySchemes(newArrayList(httpLogin()))
.securityContexts(newArrayList(securityContext())) .securityContexts(newArrayList(securityContext()))
.enableUrlTemplating(true); .enableUrlTemplating(true);
} }
private ApiKey jwtTokenKey() { @Bean
return new ApiKey("X-Authorization", "JWT token", "header"); @Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER)
ApiListingScannerPlugin loginEndpointListingScanner(final CachingOperationNameGenerator operationNames) {
return new ApiListingScannerPlugin() {
@Override
public List<ApiDescription> apply(DocumentationContext context) {
return List.of(loginEndpointApiDescription(operationNames));
}
@Override
public boolean supports(@NotNull DocumentationType delimiter) {
return DocumentationType.SWAGGER_2.equals(delimiter) || DocumentationType.OAS_30.equals(delimiter);
}
};
}
@Bean
@Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER)
ApiListingBuilderPlugin loginEndpointListingBuilder() {
return new ApiListingBuilderPlugin() {
@Override
public void apply(ApiListingContext apiListingContext) {
if (apiListingContext.getResourceGroup().getGroupName().equals("default")) {
ApiListing apiListing = apiListingContext.apiListingBuilder().build();
if (apiListing.getResourcePath().equals("/api/auth/login")) {
apiListingContext.apiListingBuilder().tags(Set.of(new Tag("login-endpoint", "Login Endpoint")));
apiListingContext.apiListingBuilder().description("Login Endpoint");
}
}
}
@Override
public boolean supports(@NotNull DocumentationType delimiter) {
return DocumentationType.SWAGGER_2.equals(delimiter) || DocumentationType.OAS_30.equals(delimiter);
}
};
}
@Bean
UiConfiguration uiConfig() {
return UiConfigurationBuilder.builder()
.deepLinking(true)
.displayOperationId(false)
.defaultModelsExpandDepth(1)
.defaultModelExpandDepth(1)
.defaultModelRendering(ModelRendering.EXAMPLE)
.displayRequestDuration(false)
.docExpansion(DocExpansion.NONE)
.filter(false)
.maxDisplayedTags(null)
.operationsSorter(OperationsSorter.ALPHA)
.showExtensions(false)
.showCommonExtensions(false)
.supportedSubmitMethods(UiConfiguration.Constants.DEFAULT_SUBMIT_METHODS)
.validatorUrl(null)
.persistAuthorization(true)
.syntaxHighlightActivate(true)
.syntaxHighlightTheme("agate")
.build();
}
private SecurityScheme httpLogin() {
return HttpLoginPasswordScheme
.X_AUTHORIZATION_BUILDER
.loginEndpoint("/api/auth/login")
.name("HTTP login form")
.description("Enter Username / Password")
.build();
} }
private SecurityContext securityContext() { private SecurityContext securityContext() {
return SecurityContext.builder() return SecurityContext.builder()
.securityReferences(defaultAuth()) .securityReferences(defaultAuth())
.forPaths(securityPaths()) .operationSelector(securityPathOperationSelector())
.build(); .build();
} }
@ -107,11 +222,8 @@ public class SwaggerConfiguration {
return regex(apiPathRegex); return regex(apiPathRegex);
} }
private Predicate<String> securityPaths() { private Predicate<OperationContext> securityPathOperationSelector() {
return and( return new SecurityPathOperationSelector(securityPathRegex, nonSecurityPathRegex);
regex(securityPathRegex),
not(regex(nonSecurityPathRegex))
);
} }
List<SecurityReference> defaultAuth() { List<SecurityReference> defaultAuth() {
@ -120,18 +232,166 @@ public class SwaggerConfiguration {
authorizationScopes[1] = new AuthorizationScope(Authority.TENANT_ADMIN.name(), "Tenant administrator"); authorizationScopes[1] = new AuthorizationScope(Authority.TENANT_ADMIN.name(), "Tenant administrator");
authorizationScopes[2] = new AuthorizationScope(Authority.CUSTOMER_USER.name(), "Customer"); authorizationScopes[2] = new AuthorizationScope(Authority.CUSTOMER_USER.name(), "Customer");
return newArrayList( return newArrayList(
new SecurityReference("X-Authorization", authorizationScopes)); new SecurityReference("HTTP login form", authorizationScopes));
} }
private ApiInfo apiInfo() { private ApiInfo apiInfo() {
String apiVersion = version;
if (StringUtils.isEmpty(apiVersion)) {
apiVersion = appVersion;
}
return new ApiInfoBuilder() return new ApiInfoBuilder()
.title(title) .title(title)
.description(description) .description(description)
.contact(new Contact(contactName, contactUrl, contactEmail)) .contact(new Contact(contactName, contactUrl, contactEmail))
.license(licenseTitle) .license(licenseTitle)
.licenseUrl(licenseUrl) .licenseUrl(licenseUrl)
.version(version) .version(apiVersion)
.build(); .build();
} }
private ApiDescription loginEndpointApiDescription(final CachingOperationNameGenerator operationNames) {
return new ApiDescription(null, "/api/auth/login", "Login method to get user JWT token data", "Login endpoint", Collections.singletonList(
new OperationBuilder(operationNames)
.summary("Login method to get user JWT token data")
.tags(Set.of("login-endpoint"))
.authorizations(new ArrayList<>())
.position(0)
.codegenMethodNameStem("loginPost")
.method(HttpMethod.POST)
.notes("Login method used to authenticate user and get JWT token data.\n\nValue of the response **token** " +
"field can be used as **X-Authorization** header value:\n\n`X-Authorization: Bearer $JWT_TOKEN_VALUE`.")
.requestParameters(
List.of(
new RequestParameterBuilder()
.in(ParameterType.BODY)
.required(true)
.description("Login request")
.content(c ->
c.requestBody(true)
.representation(MediaType.APPLICATION_JSON)
.apply(classRepresentation(LoginRequest.class, false))
)
.build()
)
)
.responses(loginResponses())
.build()
), false);
}
private Collection<Response> loginResponses() {
List<Response> responses = new ArrayList<>();
responses.add(
new ResponseBuilder()
.code("200")
.description("OK")
.representation(MediaType.APPLICATION_JSON)
.apply(classRepresentation(LoginResponse.class, true)).
build()
);
responses.addAll(loginErrorResponses());
return responses;
}
/** Helper methods **/
private List<Response> defaultErrorResponses(boolean isPost) {
return List.of(
errorResponse("400", "Bad Request",
ThingsboardErrorResponse.of(isPost ? "Invalid request body" : "Invalid UUID string: 123", ThingsboardErrorCode.BAD_REQUEST_PARAMS, HttpStatus.BAD_REQUEST)),
errorResponse("401", "Unauthorized",
ThingsboardErrorResponse.of("Authentication failed", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)),
errorResponse("403", "Forbidden",
ThingsboardErrorResponse.of("You don't have permission to perform this operation!",
ThingsboardErrorCode.PERMISSION_DENIED, HttpStatus.FORBIDDEN)),
errorResponse("404", "Not Found",
ThingsboardErrorResponse.of("Requested item wasn't found!", ThingsboardErrorCode.ITEM_NOT_FOUND, HttpStatus.NOT_FOUND)),
errorResponse("429", "Too Many Requests",
ThingsboardErrorResponse.of("Too many requests for current tenant!",
ThingsboardErrorCode.TOO_MANY_REQUESTS, HttpStatus.TOO_MANY_REQUESTS))
);
}
private List<Response> loginErrorResponses() {
return List.of(
errorResponse("401", "Unauthorized",
List.of(
errorExample("bad-credentials", "Bad credentials",
ThingsboardErrorResponse.of("Invalid username or password", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)),
errorExample("token-expired", "JWT token expired",
ThingsboardErrorResponse.of("Token has expired", ThingsboardErrorCode.JWT_TOKEN_EXPIRED, HttpStatus.UNAUTHORIZED)),
errorExample("account-disabled", "Disabled account",
ThingsboardErrorResponse.of("User account is not active", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)),
errorExample("account-locked", "Locked account",
ThingsboardErrorResponse.of("User account is locked due to security policy", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)),
errorExample("authentication-failed", "General authentication error",
ThingsboardErrorResponse.of("Authentication failed", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED))
)
),
errorResponse("401 ", "Unauthorized (**Expired credentials**)",
List.of(
errorExample("credentials-expired", "Expired credentials",
ThingsboardCredentialsExpiredResponse.of("User password expired!", RandomStringUtils.randomAlphanumeric(30)))
), ThingsboardCredentialsExpiredResponse.class
)
);
}
private Response errorResponse(String code, String description, ThingsboardErrorResponse example) {
return errorResponse(code, description, List.of(errorExample("error-code-" + code, description, example)));
}
private Response errorResponse(String code, String description, List<Example> examples) {
return errorResponse(code, description, examples, ThingsboardErrorResponse.class);
}
private Response errorResponse(String code, String description, List<Example> examples,
Class<? extends ThingsboardErrorResponse> errorResponseClass) {
return new ResponseBuilder()
.code(code)
.description(description)
.examples(examples)
.representation(MediaType.APPLICATION_JSON)
.apply(classRepresentation(errorResponseClass, true))
.build();
}
private Example errorExample(String id, String summary, ThingsboardErrorResponse example) {
return new ExampleBuilder()
.mediaType(MediaType.APPLICATION_JSON_VALUE)
.summary(summary)
.id(id)
.value(example).build();
}
private Consumer<RepresentationBuilder> classRepresentation(Class<?> clazz, boolean isResponse) {
return r -> r.model(
m ->
m.referenceModel(ref ->
ref.key(k ->
k.qualifiedModelName(q ->
q.namespace(clazz.getPackageName())
.name(clazz.getSimpleName())).isResponse(isResponse)))
);
}
private static class SecurityPathOperationSelector implements Predicate<OperationContext> {
private final Predicate<String> securityPathSelector;
SecurityPathOperationSelector(String securityPathRegex, String nonSecurityPathRegex) {
this.securityPathSelector = regex(securityPathRegex).and(
not(
regex(nonSecurityPathRegex)
));
}
@Override
public boolean test(OperationContext operationContext) {
return this.securityPathSelector.test(operationContext.requestMappingPattern());
}
}
} }

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

@ -64,6 +64,7 @@ import java.util.List;
public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapter { public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapter {
public static final String JWT_TOKEN_HEADER_PARAM = "X-Authorization"; public static final String JWT_TOKEN_HEADER_PARAM = "X-Authorization";
public static final String JWT_TOKEN_HEADER_PARAM_V2 = "Authorization";
public static final String JWT_TOKEN_QUERY_PARAM = "token"; public static final String JWT_TOKEN_QUERY_PARAM = "token";
public static final String WEBJARS_ENTRY_POINT = "/webjars/**"; public static final String WEBJARS_ENTRY_POINT = "/webjars/**";

13
application/src/main/java/org/thingsboard/server/config/WebConfig.java

@ -17,13 +17,24 @@ package org.thingsboard.server.config;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.thingsboard.server.utils.MiscUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Controller @Controller
public class WebConfig { public class WebConfig {
@RequestMapping(value = {"/assets", "/assets/", "/{path:^(?!api$)(?!assets$)(?!static$)(?!webjars$)[^\\.]*}/**"}) @RequestMapping(value = {"/assets", "/assets/", "/{path:^(?!api$)(?!assets$)(?!static$)(?!webjars$)(?!swagger-ui$)[^\\.]*}/**"})
public String redirect() { public String redirect() {
return "forward:/index.html"; return "forward:/index.html";
} }
@RequestMapping("/swagger-ui.html")
public void redirectSwagger(HttpServletRequest request, HttpServletResponse response) throws IOException {
String baseUrl = MiscUtils.constructBaseUrl(request);
response.sendRedirect(baseUrl + "/swagger-ui/");
}
} }

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

@ -73,7 +73,7 @@ public class AdminController extends BaseController {
@PathVariable("key") String key) throws ThingsboardException { @PathVariable("key") String key) throws ThingsboardException {
try { try {
accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ); accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ);
AdminSettings adminSettings = checkNotNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, key)); AdminSettings adminSettings = checkNotNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, key), "No Administration settings found for key: " + key);
if (adminSettings.getKey().equals("mail")) { if (adminSettings.getKey().equals("mail")) {
((ObjectNode) adminSettings.getJsonValue()).remove("password"); ((ObjectNode) adminSettings.getJsonValue()).remove("password");
} }

4
application/src/main/java/org/thingsboard/server/controller/AssetController.java

@ -543,7 +543,7 @@ public class AssetController extends BaseController {
notes = "Creates assignment of an existing asset to an instance of The Edge. " + notes = "Creates assignment of an existing asset to an instance of The Edge. " +
EDGE_ASSIGN_ASYNC_FIRST_STEP_DESCRIPTION + EDGE_ASSIGN_ASYNC_FIRST_STEP_DESCRIPTION +
"Second, remote edge service will receive a copy of assignment asset " + "Second, remote edge service will receive a copy of assignment asset " +
EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION + ". " + EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION +
"Third, once asset will be delivered to edge service, it's going to be available for usage on remote edge instance.", "Third, once asset will be delivered to edge service, it's going to be available for usage on remote edge instance.",
produces = MediaType.APPLICATION_JSON_VALUE) produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@ -583,7 +583,7 @@ public class AssetController extends BaseController {
notes = "Clears assignment of the asset to the edge. " + notes = "Clears assignment of the asset to the edge. " +
EDGE_UNASSIGN_ASYNC_FIRST_STEP_DESCRIPTION + EDGE_UNASSIGN_ASYNC_FIRST_STEP_DESCRIPTION +
"Second, remote edge service will receive an 'unassign' command to remove asset " + "Second, remote edge service will receive an 'unassign' command to remove asset " +
EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION + ". " + EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION +
"Third, once 'unassign' command will be delivered to edge service, it's going to remove asset locally.", "Third, once 'unassign' command will be delivered to edge service, it's going to remove asset locally.",
produces = MediaType.APPLICATION_JSON_VALUE) produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")

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

@ -310,22 +310,30 @@ public abstract class BaseController {
} else if (exception instanceof MessagingException) { } else if (exception instanceof MessagingException) {
return new ThingsboardException("Unable to send mail: " + exception.getMessage(), ThingsboardErrorCode.GENERAL); return new ThingsboardException("Unable to send mail: " + exception.getMessage(), ThingsboardErrorCode.GENERAL);
} else { } else {
return new ThingsboardException(exception.getMessage(), ThingsboardErrorCode.GENERAL); return new ThingsboardException(exception.getMessage(), exception, ThingsboardErrorCode.GENERAL);
} }
} }
<T> T checkNotNull(T reference) throws ThingsboardException { <T> T checkNotNull(T reference) throws ThingsboardException {
return checkNotNull(reference, "Requested item wasn't found!");
}
<T> T checkNotNull(T reference, String notFoundMessage) throws ThingsboardException {
if (reference == null) { if (reference == null) {
throw new ThingsboardException("Requested item wasn't found!", ThingsboardErrorCode.ITEM_NOT_FOUND); throw new ThingsboardException(notFoundMessage, ThingsboardErrorCode.ITEM_NOT_FOUND);
} }
return reference; return reference;
} }
<T> T checkNotNull(Optional<T> reference) throws ThingsboardException { <T> T checkNotNull(Optional<T> reference) throws ThingsboardException {
return checkNotNull(reference, "Requested item wasn't found!");
}
<T> T checkNotNull(Optional<T> reference, String notFoundMessage) throws ThingsboardException {
if (reference.isPresent()) { if (reference.isPresent()) {
return reference.get(); return reference.get();
} else { } else {
throw new ThingsboardException("Requested item wasn't found!", ThingsboardErrorCode.ITEM_NOT_FOUND); throw new ThingsboardException(notFoundMessage, ThingsboardErrorCode.ITEM_NOT_FOUND);
} }
} }
@ -385,7 +393,7 @@ public abstract class BaseController {
try { try {
validateId(tenantId, INCORRECT_TENANT_ID + tenantId); validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
Tenant tenant = tenantService.findTenantById(tenantId); Tenant tenant = tenantService.findTenantById(tenantId);
checkNotNull(tenant); checkNotNull(tenant, "Tenant with id [" + tenantId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.TENANT, operation, tenantId, tenant); accessControlService.checkPermission(getCurrentUser(), Resource.TENANT, operation, tenantId, tenant);
return tenant; return tenant;
} catch (Exception e) { } catch (Exception e) {
@ -397,7 +405,7 @@ public abstract class BaseController {
try { try {
validateId(tenantId, INCORRECT_TENANT_ID + tenantId); validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
TenantInfo tenant = tenantService.findTenantInfoById(tenantId); TenantInfo tenant = tenantService.findTenantInfoById(tenantId);
checkNotNull(tenant); checkNotNull(tenant, "Tenant with id [" + tenantId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.TENANT, operation, tenantId, tenant); accessControlService.checkPermission(getCurrentUser(), Resource.TENANT, operation, tenantId, tenant);
return tenant; return tenant;
} catch (Exception e) { } catch (Exception e) {
@ -409,7 +417,7 @@ public abstract class BaseController {
try { try {
validateId(tenantProfileId, "Incorrect tenantProfileId " + tenantProfileId); validateId(tenantProfileId, "Incorrect tenantProfileId " + tenantProfileId);
TenantProfile tenantProfile = tenantProfileService.findTenantProfileById(getTenantId(), tenantProfileId); TenantProfile tenantProfile = tenantProfileService.findTenantProfileById(getTenantId(), tenantProfileId);
checkNotNull(tenantProfile); checkNotNull(tenantProfile, "Tenant profile with id [" + tenantProfileId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.TENANT_PROFILE, operation); accessControlService.checkPermission(getCurrentUser(), Resource.TENANT_PROFILE, operation);
return tenantProfile; return tenantProfile;
} catch (Exception e) { } catch (Exception e) {
@ -425,7 +433,7 @@ public abstract class BaseController {
try { try {
validateId(customerId, "Incorrect customerId " + customerId); validateId(customerId, "Incorrect customerId " + customerId);
Customer customer = customerService.findCustomerById(getTenantId(), customerId); Customer customer = customerService.findCustomerById(getTenantId(), customerId);
checkNotNull(customer); checkNotNull(customer, "Customer with id [" + customerId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.CUSTOMER, operation, customerId, customer); accessControlService.checkPermission(getCurrentUser(), Resource.CUSTOMER, operation, customerId, customer);
return customer; return customer;
} catch (Exception e) { } catch (Exception e) {
@ -437,7 +445,7 @@ public abstract class BaseController {
try { try {
validateId(userId, "Incorrect userId " + userId); validateId(userId, "Incorrect userId " + userId);
User user = userService.findUserById(getCurrentUser().getTenantId(), userId); User user = userService.findUserById(getCurrentUser().getTenantId(), userId);
checkNotNull(user); checkNotNull(user, "User with id [" + userId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.USER, operation, userId, user); accessControlService.checkPermission(getCurrentUser(), Resource.USER, operation, userId, user);
return user; return user;
} catch (Exception e) { } catch (Exception e) {
@ -456,7 +464,9 @@ public abstract class BaseController {
protected void checkEntityId(EntityId entityId, Operation operation) throws ThingsboardException { protected void checkEntityId(EntityId entityId, Operation operation) throws ThingsboardException {
try { try {
checkNotNull(entityId); if (entityId == null) {
throw new ThingsboardException("Parameter entityId can't be empty!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
validateId(entityId.getId(), "Incorrect entityId " + entityId); validateId(entityId.getId(), "Incorrect entityId " + entityId);
switch (entityId.getEntityType()) { switch (entityId.getEntityType()) {
case ALARM: case ALARM:
@ -522,7 +532,7 @@ public abstract class BaseController {
try { try {
validateId(deviceId, "Incorrect deviceId " + deviceId); validateId(deviceId, "Incorrect deviceId " + deviceId);
Device device = deviceService.findDeviceById(getCurrentUser().getTenantId(), deviceId); Device device = deviceService.findDeviceById(getCurrentUser().getTenantId(), deviceId);
checkNotNull(device); checkNotNull(device, "Device with id [" + deviceId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE, operation, deviceId, device); accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE, operation, deviceId, device);
return device; return device;
} catch (Exception e) { } catch (Exception e) {
@ -534,7 +544,7 @@ public abstract class BaseController {
try { try {
validateId(deviceId, "Incorrect deviceId " + deviceId); validateId(deviceId, "Incorrect deviceId " + deviceId);
DeviceInfo device = deviceService.findDeviceInfoById(getCurrentUser().getTenantId(), deviceId); DeviceInfo device = deviceService.findDeviceInfoById(getCurrentUser().getTenantId(), deviceId);
checkNotNull(device); checkNotNull(device, "Device with id [" + deviceId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE, operation, deviceId, device); accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE, operation, deviceId, device);
return device; return device;
} catch (Exception e) { } catch (Exception e) {
@ -546,7 +556,7 @@ public abstract class BaseController {
try { try {
validateId(deviceProfileId, "Incorrect deviceProfileId " + deviceProfileId); validateId(deviceProfileId, "Incorrect deviceProfileId " + deviceProfileId);
DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(getCurrentUser().getTenantId(), deviceProfileId); DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(getCurrentUser().getTenantId(), deviceProfileId);
checkNotNull(deviceProfile); checkNotNull(deviceProfile, "Device profile with id [" + deviceProfileId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE_PROFILE, operation, deviceProfileId, deviceProfile); accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE_PROFILE, operation, deviceProfileId, deviceProfile);
return deviceProfile; return deviceProfile;
} catch (Exception e) { } catch (Exception e) {
@ -558,7 +568,7 @@ public abstract class BaseController {
try { try {
validateId(entityViewId, "Incorrect entityViewId " + entityViewId); validateId(entityViewId, "Incorrect entityViewId " + entityViewId);
EntityView entityView = entityViewService.findEntityViewById(getCurrentUser().getTenantId(), entityViewId); EntityView entityView = entityViewService.findEntityViewById(getCurrentUser().getTenantId(), entityViewId);
checkNotNull(entityView); checkNotNull(entityView, "Entity view with id [" + entityViewId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.ENTITY_VIEW, operation, entityViewId, entityView); accessControlService.checkPermission(getCurrentUser(), Resource.ENTITY_VIEW, operation, entityViewId, entityView);
return entityView; return entityView;
} catch (Exception e) { } catch (Exception e) {
@ -570,7 +580,7 @@ public abstract class BaseController {
try { try {
validateId(entityViewId, "Incorrect entityViewId " + entityViewId); validateId(entityViewId, "Incorrect entityViewId " + entityViewId);
EntityViewInfo entityView = entityViewService.findEntityViewInfoById(getCurrentUser().getTenantId(), entityViewId); EntityViewInfo entityView = entityViewService.findEntityViewInfoById(getCurrentUser().getTenantId(), entityViewId);
checkNotNull(entityView); checkNotNull(entityView, "Entity view with id [" + entityViewId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.ENTITY_VIEW, operation, entityViewId, entityView); accessControlService.checkPermission(getCurrentUser(), Resource.ENTITY_VIEW, operation, entityViewId, entityView);
return entityView; return entityView;
} catch (Exception e) { } catch (Exception e) {
@ -582,7 +592,7 @@ public abstract class BaseController {
try { try {
validateId(assetId, "Incorrect assetId " + assetId); validateId(assetId, "Incorrect assetId " + assetId);
Asset asset = assetService.findAssetById(getCurrentUser().getTenantId(), assetId); Asset asset = assetService.findAssetById(getCurrentUser().getTenantId(), assetId);
checkNotNull(asset); checkNotNull(asset, "Asset with id [" + assetId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.ASSET, operation, assetId, asset); accessControlService.checkPermission(getCurrentUser(), Resource.ASSET, operation, assetId, asset);
return asset; return asset;
} catch (Exception e) { } catch (Exception e) {
@ -594,7 +604,7 @@ public abstract class BaseController {
try { try {
validateId(assetId, "Incorrect assetId " + assetId); validateId(assetId, "Incorrect assetId " + assetId);
AssetInfo asset = assetService.findAssetInfoById(getCurrentUser().getTenantId(), assetId); AssetInfo asset = assetService.findAssetInfoById(getCurrentUser().getTenantId(), assetId);
checkNotNull(asset); checkNotNull(asset, "Asset with id [" + assetId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.ASSET, operation, assetId, asset); accessControlService.checkPermission(getCurrentUser(), Resource.ASSET, operation, assetId, asset);
return asset; return asset;
} catch (Exception e) { } catch (Exception e) {
@ -606,7 +616,7 @@ public abstract class BaseController {
try { try {
validateId(alarmId, "Incorrect alarmId " + alarmId); validateId(alarmId, "Incorrect alarmId " + alarmId);
Alarm alarm = alarmService.findAlarmByIdAsync(getCurrentUser().getTenantId(), alarmId).get(); Alarm alarm = alarmService.findAlarmByIdAsync(getCurrentUser().getTenantId(), alarmId).get();
checkNotNull(alarm); checkNotNull(alarm, "Alarm with id [" + alarmId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.ALARM, operation, alarmId, alarm); accessControlService.checkPermission(getCurrentUser(), Resource.ALARM, operation, alarmId, alarm);
return alarm; return alarm;
} catch (Exception e) { } catch (Exception e) {
@ -618,7 +628,7 @@ public abstract class BaseController {
try { try {
validateId(alarmId, "Incorrect alarmId " + alarmId); validateId(alarmId, "Incorrect alarmId " + alarmId);
AlarmInfo alarmInfo = alarmService.findAlarmInfoByIdAsync(getCurrentUser().getTenantId(), alarmId).get(); AlarmInfo alarmInfo = alarmService.findAlarmInfoByIdAsync(getCurrentUser().getTenantId(), alarmId).get();
checkNotNull(alarmInfo); checkNotNull(alarmInfo, "Alarm with id [" + alarmId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.ALARM, operation, alarmId, alarmInfo); accessControlService.checkPermission(getCurrentUser(), Resource.ALARM, operation, alarmId, alarmInfo);
return alarmInfo; return alarmInfo;
} catch (Exception e) { } catch (Exception e) {
@ -630,7 +640,7 @@ public abstract class BaseController {
try { try {
validateId(widgetsBundleId, "Incorrect widgetsBundleId " + widgetsBundleId); validateId(widgetsBundleId, "Incorrect widgetsBundleId " + widgetsBundleId);
WidgetsBundle widgetsBundle = widgetsBundleService.findWidgetsBundleById(getCurrentUser().getTenantId(), widgetsBundleId); WidgetsBundle widgetsBundle = widgetsBundleService.findWidgetsBundleById(getCurrentUser().getTenantId(), widgetsBundleId);
checkNotNull(widgetsBundle); checkNotNull(widgetsBundle, "Widgets bundle with id [" + widgetsBundleId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.WIDGETS_BUNDLE, operation, widgetsBundleId, widgetsBundle); accessControlService.checkPermission(getCurrentUser(), Resource.WIDGETS_BUNDLE, operation, widgetsBundleId, widgetsBundle);
return widgetsBundle; return widgetsBundle;
} catch (Exception e) { } catch (Exception e) {
@ -642,7 +652,7 @@ public abstract class BaseController {
try { try {
validateId(widgetTypeId, "Incorrect widgetTypeId " + widgetTypeId); validateId(widgetTypeId, "Incorrect widgetTypeId " + widgetTypeId);
WidgetTypeDetails widgetTypeDetails = widgetTypeService.findWidgetTypeDetailsById(getCurrentUser().getTenantId(), widgetTypeId); WidgetTypeDetails widgetTypeDetails = widgetTypeService.findWidgetTypeDetailsById(getCurrentUser().getTenantId(), widgetTypeId);
checkNotNull(widgetTypeDetails); checkNotNull(widgetTypeDetails, "Widget type with id [" + widgetTypeId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.WIDGET_TYPE, operation, widgetTypeId, widgetTypeDetails); accessControlService.checkPermission(getCurrentUser(), Resource.WIDGET_TYPE, operation, widgetTypeId, widgetTypeDetails);
return widgetTypeDetails; return widgetTypeDetails;
} catch (Exception e) { } catch (Exception e) {
@ -654,7 +664,7 @@ public abstract class BaseController {
try { try {
validateId(dashboardId, "Incorrect dashboardId " + dashboardId); validateId(dashboardId, "Incorrect dashboardId " + dashboardId);
Dashboard dashboard = dashboardService.findDashboardById(getCurrentUser().getTenantId(), dashboardId); Dashboard dashboard = dashboardService.findDashboardById(getCurrentUser().getTenantId(), dashboardId);
checkNotNull(dashboard); checkNotNull(dashboard, "Dashboard with id [" + dashboardId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.DASHBOARD, operation, dashboardId, dashboard); accessControlService.checkPermission(getCurrentUser(), Resource.DASHBOARD, operation, dashboardId, dashboard);
return dashboard; return dashboard;
} catch (Exception e) { } catch (Exception e) {
@ -666,7 +676,7 @@ public abstract class BaseController {
try { try {
validateId(edgeId, "Incorrect edgeId " + edgeId); validateId(edgeId, "Incorrect edgeId " + edgeId);
Edge edge = edgeService.findEdgeById(getTenantId(), edgeId); Edge edge = edgeService.findEdgeById(getTenantId(), edgeId);
checkNotNull(edge); checkNotNull(edge, "Edge with id [" + edgeId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, operation, edgeId, edge); accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, operation, edgeId, edge);
return edge; return edge;
} catch (Exception e) { } catch (Exception e) {
@ -678,7 +688,7 @@ public abstract class BaseController {
try { try {
validateId(edgeId, "Incorrect edgeId " + edgeId); validateId(edgeId, "Incorrect edgeId " + edgeId);
EdgeInfo edge = edgeService.findEdgeInfoById(getCurrentUser().getTenantId(), edgeId); EdgeInfo edge = edgeService.findEdgeInfoById(getCurrentUser().getTenantId(), edgeId);
checkNotNull(edge); checkNotNull(edge, "Edge with id [" + edgeId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, operation, edgeId, edge); accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, operation, edgeId, edge);
return edge; return edge;
} catch (Exception e) { } catch (Exception e) {
@ -690,7 +700,7 @@ public abstract class BaseController {
try { try {
validateId(dashboardId, "Incorrect dashboardId " + dashboardId); validateId(dashboardId, "Incorrect dashboardId " + dashboardId);
DashboardInfo dashboardInfo = dashboardService.findDashboardInfoById(getCurrentUser().getTenantId(), dashboardId); DashboardInfo dashboardInfo = dashboardService.findDashboardInfoById(getCurrentUser().getTenantId(), dashboardId);
checkNotNull(dashboardInfo); checkNotNull(dashboardInfo, "Dashboard with id [" + dashboardId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.DASHBOARD, operation, dashboardId, dashboardInfo); accessControlService.checkPermission(getCurrentUser(), Resource.DASHBOARD, operation, dashboardId, dashboardInfo);
return dashboardInfo; return dashboardInfo;
} catch (Exception e) { } catch (Exception e) {
@ -728,7 +738,7 @@ public abstract class BaseController {
protected RuleChain checkRuleChain(RuleChainId ruleChainId, Operation operation) throws ThingsboardException { protected RuleChain checkRuleChain(RuleChainId ruleChainId, Operation operation) throws ThingsboardException {
validateId(ruleChainId, "Incorrect ruleChainId " + ruleChainId); validateId(ruleChainId, "Incorrect ruleChainId " + ruleChainId);
RuleChain ruleChain = ruleChainService.findRuleChainById(getCurrentUser().getTenantId(), ruleChainId); RuleChain ruleChain = ruleChainService.findRuleChainById(getCurrentUser().getTenantId(), ruleChainId);
checkNotNull(ruleChain); checkNotNull(ruleChain, "Rule chain with id [" + ruleChainId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.RULE_CHAIN, operation, ruleChainId, ruleChain); accessControlService.checkPermission(getCurrentUser(), Resource.RULE_CHAIN, operation, ruleChainId, ruleChain);
return ruleChain; return ruleChain;
} }
@ -736,7 +746,7 @@ public abstract class BaseController {
protected RuleNode checkRuleNode(RuleNodeId ruleNodeId, Operation operation) throws ThingsboardException { protected RuleNode checkRuleNode(RuleNodeId ruleNodeId, Operation operation) throws ThingsboardException {
validateId(ruleNodeId, "Incorrect ruleNodeId " + ruleNodeId); validateId(ruleNodeId, "Incorrect ruleNodeId " + ruleNodeId);
RuleNode ruleNode = ruleChainService.findRuleNodeById(getTenantId(), ruleNodeId); RuleNode ruleNode = ruleChainService.findRuleNodeById(getTenantId(), ruleNodeId);
checkNotNull(ruleNode); checkNotNull(ruleNode, "Rule node with id [" + ruleNodeId + "] is not found");
checkRuleChain(ruleNode.getRuleChainId(), operation); checkRuleChain(ruleNode.getRuleChainId(), operation);
return ruleNode; return ruleNode;
} }
@ -745,7 +755,7 @@ public abstract class BaseController {
try { try {
validateId(resourceId, "Incorrect resourceId " + resourceId); validateId(resourceId, "Incorrect resourceId " + resourceId);
TbResource resource = resourceService.findResourceById(getCurrentUser().getTenantId(), resourceId); TbResource resource = resourceService.findResourceById(getCurrentUser().getTenantId(), resourceId);
checkNotNull(resource); checkNotNull(resource, "Resource with id [" + resourceId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.TB_RESOURCE, operation, resourceId, resource); accessControlService.checkPermission(getCurrentUser(), Resource.TB_RESOURCE, operation, resourceId, resource);
return resource; return resource;
} catch (Exception e) { } catch (Exception e) {
@ -757,7 +767,7 @@ public abstract class BaseController {
try { try {
validateId(resourceId, "Incorrect resourceId " + resourceId); validateId(resourceId, "Incorrect resourceId " + resourceId);
TbResourceInfo resourceInfo = resourceService.findResourceInfoById(getCurrentUser().getTenantId(), resourceId); TbResourceInfo resourceInfo = resourceService.findResourceInfoById(getCurrentUser().getTenantId(), resourceId);
checkNotNull(resourceInfo); checkNotNull(resourceInfo, "Resource with id [" + resourceId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.TB_RESOURCE, operation, resourceId, resourceInfo); accessControlService.checkPermission(getCurrentUser(), Resource.TB_RESOURCE, operation, resourceId, resourceInfo);
return resourceInfo; return resourceInfo;
} catch (Exception e) { } catch (Exception e) {
@ -769,7 +779,7 @@ public abstract class BaseController {
try { try {
validateId(otaPackageId, "Incorrect otaPackageId " + otaPackageId); validateId(otaPackageId, "Incorrect otaPackageId " + otaPackageId);
OtaPackage otaPackage = otaPackageService.findOtaPackageById(getCurrentUser().getTenantId(), otaPackageId); OtaPackage otaPackage = otaPackageService.findOtaPackageById(getCurrentUser().getTenantId(), otaPackageId);
checkNotNull(otaPackage); checkNotNull(otaPackage, "OTA package with id [" + otaPackageId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.OTA_PACKAGE, operation, otaPackageId, otaPackage); accessControlService.checkPermission(getCurrentUser(), Resource.OTA_PACKAGE, operation, otaPackageId, otaPackage);
return otaPackage; return otaPackage;
} catch (Exception e) { } catch (Exception e) {
@ -781,7 +791,7 @@ public abstract class BaseController {
try { try {
validateId(otaPackageId, "Incorrect otaPackageId " + otaPackageId); validateId(otaPackageId, "Incorrect otaPackageId " + otaPackageId);
OtaPackageInfo otaPackageIn = otaPackageService.findOtaPackageInfoById(getCurrentUser().getTenantId(), otaPackageId); OtaPackageInfo otaPackageIn = otaPackageService.findOtaPackageInfoById(getCurrentUser().getTenantId(), otaPackageId);
checkNotNull(otaPackageIn); checkNotNull(otaPackageIn, "OTA package with id [" + otaPackageId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.OTA_PACKAGE, operation, otaPackageId, otaPackageIn); accessControlService.checkPermission(getCurrentUser(), Resource.OTA_PACKAGE, operation, otaPackageId, otaPackageIn);
return otaPackageIn; return otaPackageIn;
} catch (Exception e) { } catch (Exception e) {
@ -793,7 +803,7 @@ public abstract class BaseController {
try { try {
validateId(rpcId, "Incorrect rpcId " + rpcId); validateId(rpcId, "Incorrect rpcId " + rpcId);
Rpc rpc = rpcService.findById(getCurrentUser().getTenantId(), rpcId); Rpc rpc = rpcService.findById(getCurrentUser().getTenantId(), rpcId);
checkNotNull(rpc); checkNotNull(rpc, "RPC with id [" + rpcId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.RPC, operation, rpcId, rpc); accessControlService.checkPermission(getCurrentUser(), Resource.RPC, operation, rpcId, rpc);
return rpc; return rpc;
} catch (Exception e) { } catch (Exception e) {

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

@ -24,6 +24,7 @@ public class ControllerConstants {
protected static final String CUSTOMER_ID = "customerId"; protected static final String CUSTOMER_ID = "customerId";
protected static final String TENANT_ID = "tenantId"; protected static final String TENANT_ID = "tenantId";
protected static final String DEVICE_ID = "deviceId"; protected static final String DEVICE_ID = "deviceId";
protected static final String EDGE_ID = "edgeId";
protected static final String RPC_ID = "rpcId"; protected static final String RPC_ID = "rpcId";
protected static final String ENTITY_ID = "entityId"; protected static final String ENTITY_ID = "entityId";
protected static final String PAGE_DATA_PARAMETERS = "You can specify parameters to filter the results. " + protected static final String PAGE_DATA_PARAMETERS = "You can specify parameters to filter the results. " +
@ -45,6 +46,7 @@ public class ControllerConstants {
protected static final String OTA_PACKAGE_ID_PARAM_DESCRIPTION = "A string value representing the ota package id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; protected static final String OTA_PACKAGE_ID_PARAM_DESCRIPTION = "A string value representing the ota package id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
protected static final String ENTITY_TYPE_PARAM_DESCRIPTION = "A string value representing the entity type. For example, 'DEVICE'"; protected static final String ENTITY_TYPE_PARAM_DESCRIPTION = "A string value representing the entity type. For example, 'DEVICE'";
protected static final String RULE_CHAIN_ID_PARAM_DESCRIPTION = "A string value representing the rule chain id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; protected static final String RULE_CHAIN_ID_PARAM_DESCRIPTION = "A string value representing the rule chain id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
protected static final String RULE_NODE_ID_PARAM_DESCRIPTION = "A string value representing the rule node id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
protected static final String WIDGET_BUNDLE_ID_PARAM_DESCRIPTION = "A string value representing the widget bundle id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; protected static final String WIDGET_BUNDLE_ID_PARAM_DESCRIPTION = "A string value representing the widget bundle id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
protected static final String WIDGET_TYPE_ID_PARAM_DESCRIPTION = "A string value representing the widget type id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; protected static final String WIDGET_TYPE_ID_PARAM_DESCRIPTION = "A string value representing the widget type id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
protected static final String RESOURCE_ID_PARAM_DESCRIPTION = "A string value representing the resource id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; protected static final String RESOURCE_ID_PARAM_DESCRIPTION = "A string value representing the resource id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
@ -129,9 +131,9 @@ public class ControllerConstants {
protected static final String EVENT_END_TIME_DESCRIPTION = "Timestamp. Events with creation time after it won't be queried."; protected static final String EVENT_END_TIME_DESCRIPTION = "Timestamp. Events with creation time after it won't be queried.";
protected static final String EDGE_UNASSIGN_ASYNC_FIRST_STEP_DESCRIPTION = "Unassignment works in async way - first, 'unassign' notification event pushed to edge queue on platform. "; protected static final String EDGE_UNASSIGN_ASYNC_FIRST_STEP_DESCRIPTION = "Unassignment works in async way - first, 'unassign' notification event pushed to edge queue on platform. ";
protected static final String EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION = "(Edge will receive this instantly, if it's currently connected, or once it's going to be connected to platform)"; protected static final String EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION = "(Edge will receive this instantly, if it's currently connected, or once it's going to be connected to platform). ";
protected static final String EDGE_ASSIGN_ASYNC_FIRST_STEP_DESCRIPTION = "Assignment works in async way - first, notification event pushed to edge service queue on platform. "; protected static final String EDGE_ASSIGN_ASYNC_FIRST_STEP_DESCRIPTION = "Assignment works in async way - first, notification event pushed to edge service queue on platform. ";
protected static final String EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION = "(Edge will receive this instantly, if it's currently connected, or once it's going to be connected to platform)"; protected static final String EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION = "(Edge will receive this instantly, if it's currently connected, or once it's going to be connected to platform). ";
protected static final String MARKDOWN_CODE_BLOCK_START = "```json\n"; protected static final String MARKDOWN_CODE_BLOCK_START = "```json\n";
protected static final String MARKDOWN_CODE_BLOCK_END = "\n```"; protected static final String MARKDOWN_CODE_BLOCK_END = "\n```";

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

@ -240,7 +240,7 @@ public class CustomerController extends BaseController {
@RequestParam String customerTitle) throws ThingsboardException { @RequestParam String customerTitle) throws ThingsboardException {
try { try {
TenantId tenantId = getCurrentUser().getTenantId(); TenantId tenantId = getCurrentUser().getTenantId();
return checkNotNull(customerService.findCustomerByTenantIdAndTitle(tenantId, customerTitle)); return checkNotNull(customerService.findCustomerByTenantIdAndTitle(tenantId, customerTitle), "Customer with title [" + customerTitle + "] is not found");
} catch (Exception e) { } catch (Exception e) {
throw handleException(e); throw handleException(e);
} }

30
application/src/main/java/org/thingsboard/server/controller/DashboardController.java

@ -19,6 +19,9 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam; import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.Example;
import io.swagger.annotations.ExampleProperty;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
@ -51,7 +54,6 @@ import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Operation;
@ -69,6 +71,8 @@ import static org.thingsboard.server.controller.ControllerConstants.DASHBOARD_SO
import static org.thingsboard.server.controller.ControllerConstants.DASHBOARD_TEXT_SEARCH_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.DASHBOARD_TEXT_SEARCH_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.EDGE_ASSIGN_ASYNC_FIRST_STEP_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.EDGE_ASSIGN_ASYNC_FIRST_STEP_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.EDGE_ID;
import static org.thingsboard.server.controller.ControllerConstants.EDGE_ID_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.EDGE_UNASSIGN_ASYNC_FIRST_STEP_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.EDGE_UNASSIGN_ASYNC_FIRST_STEP_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS; import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS;
@ -105,6 +109,7 @@ public class DashboardController extends BaseController {
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/dashboard/serverTime", method = RequestMethod.GET) @RequestMapping(value = "/dashboard/serverTime", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ApiResponse(code = 200, message = "OK", examples = @Example(value = @ExampleProperty(value = "1636023857137", mediaType = "application/json")))
public long getServerTime() throws ThingsboardException { public long getServerTime() throws ThingsboardException {
return System.currentTimeMillis(); return System.currentTimeMillis();
} }
@ -117,6 +122,7 @@ public class DashboardController extends BaseController {
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/dashboard/maxDatapointsLimit", method = RequestMethod.GET) @RequestMapping(value = "/dashboard/maxDatapointsLimit", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ApiResponse(code = 200, message = "OK", examples = @Example(value = @ExampleProperty(value = "5000", mediaType = "application/json")))
public long getMaxDatapointsLimit() throws ThingsboardException { public long getMaxDatapointsLimit() throws ThingsboardException {
return maxDatapointsLimit; return maxDatapointsLimit;
} }
@ -829,7 +835,7 @@ public class DashboardController extends BaseController {
notes = "Creates assignment of an existing dashboard to an instance of The Edge. " + notes = "Creates assignment of an existing dashboard to an instance of The Edge. " +
EDGE_ASSIGN_ASYNC_FIRST_STEP_DESCRIPTION + EDGE_ASSIGN_ASYNC_FIRST_STEP_DESCRIPTION +
"Second, remote edge service will receive a copy of assignment dashboard " + "Second, remote edge service will receive a copy of assignment dashboard " +
EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION + ". " + EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION +
"Third, once dashboard will be delivered to edge service, it's going to be available for usage on remote edge instance." + "Third, once dashboard will be delivered to edge service, it's going to be available for usage on remote edge instance." +
TENANT_AUTHORITY_PARAGRAPH, TENANT_AUTHORITY_PARAGRAPH,
produces = MediaType.APPLICATION_JSON_VALUE) produces = MediaType.APPLICATION_JSON_VALUE)
@ -870,7 +876,7 @@ public class DashboardController extends BaseController {
notes = "Clears assignment of the dashboard to the edge. " + notes = "Clears assignment of the dashboard to the edge. " +
EDGE_UNASSIGN_ASYNC_FIRST_STEP_DESCRIPTION + EDGE_UNASSIGN_ASYNC_FIRST_STEP_DESCRIPTION +
"Second, remote edge service will receive an 'unassign' command to remove dashboard " + "Second, remote edge service will receive an 'unassign' command to remove dashboard " +
EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION + ". " + EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION +
"Third, once 'unassign' command will be delivered to edge service, it's going to remove dashboard locally." + "Third, once 'unassign' command will be delivered to edge service, it's going to remove dashboard locally." +
TENANT_AUTHORITY_PARAGRAPH, TENANT_AUTHORITY_PARAGRAPH,
produces = MediaType.APPLICATION_JSON_VALUE) produces = MediaType.APPLICATION_JSON_VALUE)
@ -906,24 +912,32 @@ public class DashboardController extends BaseController {
} }
} }
@ApiOperation(value = "Get Edge Dashboards (getEdgeDashboards)",
notes = "Returns a page of dashboard info objects assigned to the specified edge. "
+ DASHBOARD_INFO_DEFINITION + " " + PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/edge/{edgeId}/dashboards", params = {"pageSize", "page"}, method = RequestMethod.GET) @RequestMapping(value = "/edge/{edgeId}/dashboards", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public PageData<DashboardInfo> getEdgeDashboards( public PageData<DashboardInfo> getEdgeDashboards(
@PathVariable("edgeId") String strEdgeId, @ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true)
@PathVariable(EDGE_ID) String strEdgeId,
@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
@RequestParam int pageSize, @RequestParam int pageSize,
@ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
@RequestParam int page, @RequestParam int page,
@ApiParam(value = DASHBOARD_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch, @RequestParam(required = false) String textSearch,
@ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = DASHBOARD_SORT_PROPERTY_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortProperty, @RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder, @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES)
@RequestParam(required = false) Long startTime, @RequestParam(required = false) String sortOrder) throws ThingsboardException {
@RequestParam(required = false) Long endTime) throws ThingsboardException {
checkParameter("edgeId", strEdgeId); checkParameter("edgeId", strEdgeId);
try { try {
TenantId tenantId = getCurrentUser().getTenantId(); TenantId tenantId = getCurrentUser().getTenantId();
EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
checkEdgeId(edgeId, Operation.READ); checkEdgeId(edgeId, Operation.READ);
TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime); PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
PageData<DashboardInfo> nonFilteredResult = dashboardService.findDashboardsByTenantIdAndEdgeId(tenantId, edgeId, pageLink); PageData<DashboardInfo> nonFilteredResult = dashboardService.findDashboardsByTenantIdAndEdgeId(tenantId, edgeId, pageLink);
List<DashboardInfo> filteredDashboards = nonFilteredResult.getData().stream().filter(dashboardInfo -> { List<DashboardInfo> filteredDashboards = nonFilteredResult.getData().stream().filter(dashboardInfo -> {
try { try {

4
application/src/main/java/org/thingsboard/server/controller/DeviceController.java

@ -850,7 +850,7 @@ public class DeviceController extends BaseController {
notes = "Creates assignment of an existing device to an instance of The Edge. " + notes = "Creates assignment of an existing device to an instance of The Edge. " +
EDGE_ASSIGN_ASYNC_FIRST_STEP_DESCRIPTION + EDGE_ASSIGN_ASYNC_FIRST_STEP_DESCRIPTION +
"Second, remote edge service will receive a copy of assignment device " + "Second, remote edge service will receive a copy of assignment device " +
EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION + ". " + EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION +
"Third, once device will be delivered to edge service, it's going to be available for usage on remote edge instance." + TENANT_AUTHORITY_PARAGRAPH, "Third, once device will be delivered to edge service, it's going to be available for usage on remote edge instance." + TENANT_AUTHORITY_PARAGRAPH,
produces = MediaType.APPLICATION_JSON_VALUE) produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@ -893,7 +893,7 @@ public class DeviceController extends BaseController {
notes = "Clears assignment of the device to the edge. " + notes = "Clears assignment of the device to the edge. " +
EDGE_UNASSIGN_ASYNC_FIRST_STEP_DESCRIPTION + EDGE_UNASSIGN_ASYNC_FIRST_STEP_DESCRIPTION +
"Second, remote edge service will receive an 'unassign' command to remove device " + "Second, remote edge service will receive an 'unassign' command to remove device " +
EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION + ". " + EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION +
"Third, once 'unassign' command will be delivered to edge service, it's going to remove device locally." + TENANT_AUTHORITY_PARAGRAPH, "Third, once 'unassign' command will be delivered to edge service, it's going to remove device locally." + TENANT_AUTHORITY_PARAGRAPH,
produces = MediaType.APPLICATION_JSON_VALUE) produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")

16
application/src/main/java/org/thingsboard/server/controller/EdgeController.java

@ -453,17 +453,17 @@ public class EdgeController extends BaseController {
} }
} }
@ApiOperation(value = "Set root rule chain for provided edge (setRootRuleChain)", @ApiOperation(value = "Set root rule chain for provided edge (setEdgeRootRuleChain)",
notes = "Change root rule chain of the edge to the new provided rule chain. \n" + notes = "Change root rule chain of the edge to the new provided rule chain. \n" +
"This operation will send a notification to update root rule chain on remote edge service." + TENANT_AUTHORITY_PARAGRAPH, "This operation will send a notification to update root rule chain on remote edge service." + TENANT_AUTHORITY_PARAGRAPH,
produces = MediaType.APPLICATION_JSON_VALUE) produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}/{ruleChainId}/root", method = RequestMethod.POST) @RequestMapping(value = "/edge/{edgeId}/{ruleChainId}/root", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Edge setRootRuleChain(@ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true) public Edge setEdgeRootRuleChain(@ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true)
@PathVariable(EDGE_ID) String strEdgeId, @PathVariable(EDGE_ID) String strEdgeId,
@ApiParam(value = RULE_CHAIN_ID_PARAM_DESCRIPTION, required = true) @ApiParam(value = RULE_CHAIN_ID_PARAM_DESCRIPTION, required = true)
@PathVariable("ruleChainId") String strRuleChainId) throws ThingsboardException { @PathVariable("ruleChainId") String strRuleChainId) throws ThingsboardException {
checkParameter(EDGE_ID, strEdgeId); checkParameter(EDGE_ID, strEdgeId);
checkParameter("ruleChainId", strRuleChainId); checkParameter("ruleChainId", strRuleChainId);
try { try {
@ -737,6 +737,9 @@ public class EdgeController extends BaseController {
edge.setEdgeLicenseKey(null); edge.setEdgeLicenseKey(null);
} }
@ApiOperation(value = "Check edge license (checkInstance)",
notes = "Checks license request from edge service by forwarding request to license portal.",
produces = MediaType.APPLICATION_JSON_VALUE)
@RequestMapping(value = "/license/checkInstance", method = RequestMethod.POST) @RequestMapping(value = "/license/checkInstance", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public ResponseEntity<JsonNode> checkInstance(@RequestBody JsonNode request) throws ThingsboardException { public ResponseEntity<JsonNode> checkInstance(@RequestBody JsonNode request) throws ThingsboardException {
@ -749,6 +752,9 @@ public class EdgeController extends BaseController {
} }
} }
@ApiOperation(value = "Activate edge instance (activateInstance)",
notes = "Activates edge license on license portal.",
produces = MediaType.APPLICATION_JSON_VALUE)
@RequestMapping(value = "/license/activateInstance", params = {"licenseSecret", "releaseDate"}, method = RequestMethod.POST) @RequestMapping(value = "/license/activateInstance", params = {"licenseSecret", "releaseDate"}, method = RequestMethod.POST)
@ResponseBody @ResponseBody
public ResponseEntity<JsonNode> activateInstance(@RequestParam String licenseSecret, public ResponseEntity<JsonNode> activateInstance(@RequestParam String licenseSecret,

4
application/src/main/java/org/thingsboard/server/controller/EntityViewController.java

@ -728,7 +728,7 @@ public class EntityViewController extends BaseController {
notes = "Creates assignment of an existing entity view to an instance of The Edge. " + notes = "Creates assignment of an existing entity view to an instance of The Edge. " +
EDGE_ASSIGN_ASYNC_FIRST_STEP_DESCRIPTION + EDGE_ASSIGN_ASYNC_FIRST_STEP_DESCRIPTION +
"Second, remote edge service will receive a copy of assignment entity view " + "Second, remote edge service will receive a copy of assignment entity view " +
EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION + ". " + EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION +
"Third, once entity view will be delivered to edge service, it's going to be available for usage on remote edge instance.", "Third, once entity view will be delivered to edge service, it's going to be available for usage on remote edge instance.",
produces = MediaType.APPLICATION_JSON_VALUE) produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@ -765,7 +765,7 @@ public class EntityViewController extends BaseController {
notes = "Clears assignment of the entity view to the edge. " + notes = "Clears assignment of the entity view to the edge. " +
EDGE_UNASSIGN_ASYNC_FIRST_STEP_DESCRIPTION + EDGE_UNASSIGN_ASYNC_FIRST_STEP_DESCRIPTION +
"Second, remote edge service will receive an 'unassign' command to remove entity view " + "Second, remote edge service will receive an 'unassign' command to remove entity view " +
EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION + ". " + EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION +
"Third, once 'unassign' command will be delivered to edge service, it's going to remove entity view locally.", "Third, once 'unassign' command will be delivered to edge service, it's going to remove entity view locally.",
produces = MediaType.APPLICATION_JSON_VALUE) produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")

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

@ -83,6 +83,8 @@ import java.util.stream.Collectors;
import static org.thingsboard.server.controller.ControllerConstants.EDGE_ASSIGN_ASYNC_FIRST_STEP_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.EDGE_ASSIGN_ASYNC_FIRST_STEP_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.EDGE_ID;
import static org.thingsboard.server.controller.ControllerConstants.EDGE_ID_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.EDGE_UNASSIGN_ASYNC_FIRST_STEP_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.EDGE_UNASSIGN_ASYNC_FIRST_STEP_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.MARKDOWN_CODE_BLOCK_END; import static org.thingsboard.server.controller.ControllerConstants.MARKDOWN_CODE_BLOCK_END;
@ -95,6 +97,7 @@ import static org.thingsboard.server.controller.ControllerConstants.RULE_CHAIN_S
import static org.thingsboard.server.controller.ControllerConstants.RULE_CHAIN_TEXT_SEARCH_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.RULE_CHAIN_TEXT_SEARCH_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.RULE_CHAIN_TYPES_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.RULE_CHAIN_TYPES_ALLOWABLE_VALUES;
import static org.thingsboard.server.controller.ControllerConstants.RULE_CHAIN_TYPE_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.RULE_CHAIN_TYPE_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.RULE_NODE_ID_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES;
import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION;
@ -435,7 +438,7 @@ public class RuleChainController extends BaseController {
@RequestMapping(value = "/ruleNode/{ruleNodeId}/debugIn", method = RequestMethod.GET) @RequestMapping(value = "/ruleNode/{ruleNodeId}/debugIn", method = RequestMethod.GET)
@ResponseBody @ResponseBody
public JsonNode getLatestRuleNodeDebugInput( public JsonNode getLatestRuleNodeDebugInput(
@ApiParam(value = RULE_CHAIN_ID_PARAM_DESCRIPTION) @ApiParam(value = RULE_NODE_ID_PARAM_DESCRIPTION)
@PathVariable(RULE_NODE_ID) String strRuleNodeId) throws ThingsboardException { @PathVariable(RULE_NODE_ID) String strRuleNodeId) throws ThingsboardException {
checkParameter(RULE_NODE_ID, strRuleNodeId); checkParameter(RULE_NODE_ID, strRuleNodeId);
try { try {
@ -601,7 +604,7 @@ public class RuleChainController extends BaseController {
notes = "Creates assignment of an existing rule chain to an instance of The Edge. " + notes = "Creates assignment of an existing rule chain to an instance of The Edge. " +
EDGE_ASSIGN_ASYNC_FIRST_STEP_DESCRIPTION + EDGE_ASSIGN_ASYNC_FIRST_STEP_DESCRIPTION +
"Second, remote edge service will receive a copy of assignment rule chain " + "Second, remote edge service will receive a copy of assignment rule chain " +
EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION + ". " + EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION +
"Third, once rule chain will be delivered to edge service, it's going to start processing messages locally. " + "Third, once rule chain will be delivered to edge service, it's going to start processing messages locally. " +
"\n\nOnly rule chain with type 'EDGE' can be assigned to edge." + TENANT_AUTHORITY_PARAGRAPH, "\n\nOnly rule chain with type 'EDGE' can be assigned to edge." + TENANT_AUTHORITY_PARAGRAPH,
produces = MediaType.APPLICATION_JSON_VALUE) produces = MediaType.APPLICATION_JSON_VALUE)
@ -642,7 +645,7 @@ public class RuleChainController extends BaseController {
notes = "Clears assignment of the rule chain to the edge. " + notes = "Clears assignment of the rule chain to the edge. " +
EDGE_UNASSIGN_ASYNC_FIRST_STEP_DESCRIPTION + EDGE_UNASSIGN_ASYNC_FIRST_STEP_DESCRIPTION +
"Second, remote edge service will receive an 'unassign' command to remove rule chain " + "Second, remote edge service will receive an 'unassign' command to remove rule chain " +
EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION + ". " + EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION +
"Third, once 'unassign' command will be delivered to edge service, it's going to remove rule chain locally." + TENANT_AUTHORITY_PARAGRAPH, "Third, once 'unassign' command will be delivered to edge service, it's going to remove rule chain locally." + TENANT_AUTHORITY_PARAGRAPH,
produces = MediaType.APPLICATION_JSON_VALUE) produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@ -677,17 +680,25 @@ public class RuleChainController extends BaseController {
} }
} }
@ApiOperation(value = "Get Edge Rule Chains (getEdgeRuleChains)",
notes = "Returns a page of Rule Chains assigned to the specified edge. " + RULE_CHAIN_DESCRIPTION + PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}/ruleChains", params = {"pageSize", "page"}, method = RequestMethod.GET) @RequestMapping(value = "/edge/{edgeId}/ruleChains", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public PageData<RuleChain> getEdgeRuleChains( public PageData<RuleChain> getEdgeRuleChains(
@PathVariable("edgeId") String strEdgeId, @ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true)
@PathVariable(EDGE_ID) String strEdgeId,
@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
@RequestParam int pageSize, @RequestParam int pageSize,
@ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
@RequestParam int page, @RequestParam int page,
@ApiParam(value = RULE_CHAIN_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch, @RequestParam(required = false) String textSearch,
@ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = RULE_CHAIN_SORT_PROPERTY_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortProperty, @RequestParam(required = false) String sortProperty,
@ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortOrder) throws ThingsboardException { @RequestParam(required = false) String sortOrder) throws ThingsboardException {
checkParameter("edgeId", strEdgeId); checkParameter(EDGE_ID, strEdgeId);
try { try {
TenantId tenantId = getCurrentUser().getTenantId(); TenantId tenantId = getCurrentUser().getTenantId();
EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
@ -699,10 +710,14 @@ public class RuleChainController extends BaseController {
} }
} }
@ApiOperation(value = "Set Edge Template Root Rule Chain (setEdgeTemplateRootRuleChain)",
notes = "Makes the rule chain to be root rule chain for any new edge that will be created. " +
"Does not update root rule chain for already created edges. " + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/ruleChain/{ruleChainId}/edgeTemplateRoot", method = RequestMethod.POST) @RequestMapping(value = "/ruleChain/{ruleChainId}/edgeTemplateRoot", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public RuleChain setEdgeTemplateRootRuleChain(@PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException { public RuleChain setEdgeTemplateRootRuleChain(@ApiParam(value = RULE_CHAIN_ID_PARAM_DESCRIPTION)
@PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException {
checkParameter(RULE_CHAIN_ID, strRuleChainId); checkParameter(RULE_CHAIN_ID, strRuleChainId);
try { try {
RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId)); RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId));
@ -718,10 +733,14 @@ public class RuleChainController extends BaseController {
} }
} }
@ApiOperation(value = "Set Auto Assign To Edge Rule Chain (setAutoAssignToEdgeRuleChain)",
notes = "Makes the rule chain to be automatically assigned for any new edge that will be created. " +
"Does not assign this rule chain for already created edges. " + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/ruleChain/{ruleChainId}/autoAssignToEdge", method = RequestMethod.POST) @RequestMapping(value = "/ruleChain/{ruleChainId}/autoAssignToEdge", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public RuleChain setAutoAssignToEdgeRuleChain(@PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException { public RuleChain setAutoAssignToEdgeRuleChain(@ApiParam(value = RULE_CHAIN_ID_PARAM_DESCRIPTION)
@PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException {
checkParameter(RULE_CHAIN_ID, strRuleChainId); checkParameter(RULE_CHAIN_ID, strRuleChainId);
try { try {
RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId)); RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId));
@ -737,10 +756,14 @@ public class RuleChainController extends BaseController {
} }
} }
@ApiOperation(value = "Unset Auto Assign To Edge Rule Chain (unsetAutoAssignToEdgeRuleChain)",
notes = "Removes the rule chain from the list of rule chains that are going to be automatically assigned for any new edge that will be created. " +
"Does not unassign this rule chain for already assigned edges. " + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/ruleChain/{ruleChainId}/autoAssignToEdge", method = RequestMethod.DELETE) @RequestMapping(value = "/ruleChain/{ruleChainId}/autoAssignToEdge", method = RequestMethod.DELETE)
@ResponseBody @ResponseBody
public RuleChain unsetAutoAssignToEdgeRuleChain(@PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException { public RuleChain unsetAutoAssignToEdgeRuleChain(@ApiParam(value = RULE_CHAIN_ID_PARAM_DESCRIPTION)
@PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException {
checkParameter(RULE_CHAIN_ID, strRuleChainId); checkParameter(RULE_CHAIN_ID, strRuleChainId);
try { try {
RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId)); RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId));
@ -757,6 +780,8 @@ public class RuleChainController extends BaseController {
} }
// TODO: @voba refactor this - add new config to edge rule chain to set it as auto-assign // TODO: @voba refactor this - add new config to edge rule chain to set it as auto-assign
@ApiOperation(value = "Get Auto Assign To Edge Rule Chains (getAutoAssignToEdgeRuleChains)",
notes = "Returns a list of Rule Chains that will be assigned to a newly created edge. " + RULE_CHAIN_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/ruleChain/autoAssignToEdgeRuleChains", method = RequestMethod.GET) @RequestMapping(value = "/ruleChain/autoAssignToEdgeRuleChains", method = RequestMethod.GET)
@ResponseBody @ResponseBody

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

@ -76,6 +76,7 @@ import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.common.transport.adaptor.JsonConverter;
import org.thingsboard.server.dao.service.ConstraintValidator;
import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.AccessValidator; import org.thingsboard.server.service.security.AccessValidator;
@ -184,7 +185,11 @@ public class TelemetryController extends BaseController {
public DeferredResult<ResponseEntity> getAttributeKeys( public DeferredResult<ResponseEntity> getAttributeKeys(
@ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType, @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType,
@ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr) throws ThingsboardException { @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr) throws ThingsboardException {
return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr, this::getAttributeKeysCallback); try {
return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr, this::getAttributeKeysCallback);
} catch (Exception e) {
throw handleException(e);
}
} }
@ApiOperation(value = "Get all attribute keys by scope (getAttributeKeysByScope)", @ApiOperation(value = "Get all attribute keys by scope (getAttributeKeysByScope)",
@ -201,8 +206,12 @@ public class TelemetryController extends BaseController {
@ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType, @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType,
@ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
@ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, required = true, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES) @PathVariable("scope") String scope) throws ThingsboardException { @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, required = true, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES) @PathVariable("scope") String scope) throws ThingsboardException {
return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr, try {
(result, tenantId, entityId) -> getAttributeKeysCallback(result, tenantId, entityId, scope)); return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr,
(result, tenantId, entityId) -> getAttributeKeysCallback(result, tenantId, entityId, scope));
} catch (Exception e) {
throw handleException(e);
}
} }
@ApiOperation(value = "Get attributes (getAttributes)", @ApiOperation(value = "Get attributes (getAttributes)",
@ -220,9 +229,13 @@ public class TelemetryController extends BaseController {
@ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType, @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType,
@ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
@ApiParam(value = ATTRIBUTES_KEYS_DESCRIPTION) @RequestParam(name = "keys", required = false) String keysStr) throws ThingsboardException { @ApiParam(value = ATTRIBUTES_KEYS_DESCRIPTION) @RequestParam(name = "keys", required = false) String keysStr) throws ThingsboardException {
SecurityUser user = getCurrentUser(); try {
return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr, SecurityUser user = getCurrentUser();
(result, tenantId, entityId) -> getAttributeValuesCallback(result, user, entityId, null, keysStr)); return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr,
(result, tenantId, entityId) -> getAttributeValuesCallback(result, user, entityId, null, keysStr));
} catch (Exception e) {
throw handleException(e);
}
} }
@ -244,9 +257,13 @@ public class TelemetryController extends BaseController {
@ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
@ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES, required = true) @PathVariable("scope") String scope, @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES, required = true) @PathVariable("scope") String scope,
@ApiParam(value = ATTRIBUTES_KEYS_DESCRIPTION) @RequestParam(name = "keys", required = false) String keysStr) throws ThingsboardException { @ApiParam(value = ATTRIBUTES_KEYS_DESCRIPTION) @RequestParam(name = "keys", required = false) String keysStr) throws ThingsboardException {
SecurityUser user = getCurrentUser(); try {
return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr, SecurityUser user = getCurrentUser();
(result, tenantId, entityId) -> getAttributeValuesCallback(result, user, entityId, scope, keysStr)); return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr,
(result, tenantId, entityId) -> getAttributeValuesCallback(result, user, entityId, scope, keysStr));
} catch (Exception e) {
throw handleException(e);
}
} }
@ApiOperation(value = "Get time-series keys (getTimeseriesKeys)", @ApiOperation(value = "Get time-series keys (getTimeseriesKeys)",
@ -259,8 +276,12 @@ public class TelemetryController extends BaseController {
public DeferredResult<ResponseEntity> getTimeseriesKeys( public DeferredResult<ResponseEntity> getTimeseriesKeys(
@ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType, @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType,
@ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr) throws ThingsboardException { @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr) throws ThingsboardException {
return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr, try {
(result, tenantId, entityId) -> Futures.addCallback(tsService.findAllLatest(tenantId, entityId), getTsKeysToResponseCallback(result), MoreExecutors.directExecutor())); return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr,
(result, tenantId, entityId) -> Futures.addCallback(tsService.findAllLatest(tenantId, entityId), getTsKeysToResponseCallback(result), MoreExecutors.directExecutor()));
} catch (Exception e) {
throw handleException(e);
}
} }
@ApiOperation(value = "Get latest time-series value (getLatestTimeseries)", @ApiOperation(value = "Get latest time-series value (getLatestTimeseries)",
@ -285,9 +306,13 @@ public class TelemetryController extends BaseController {
@ApiParam(value = TELEMETRY_KEYS_DESCRIPTION) @RequestParam(name = "keys", required = false) String keysStr, @ApiParam(value = TELEMETRY_KEYS_DESCRIPTION) @RequestParam(name = "keys", required = false) String keysStr,
@ApiParam(value = STRICT_DATA_TYPES_DESCRIPTION) @ApiParam(value = STRICT_DATA_TYPES_DESCRIPTION)
@RequestParam(name = "useStrictDataTypes", required = false, defaultValue = "false") Boolean useStrictDataTypes) throws ThingsboardException { @RequestParam(name = "useStrictDataTypes", required = false, defaultValue = "false") Boolean useStrictDataTypes) throws ThingsboardException {
SecurityUser user = getCurrentUser(); try {
return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr, SecurityUser user = getCurrentUser();
(result, tenantId, entityId) -> getLatestTimeseriesValuesCallback(result, user, entityId, keysStr, useStrictDataTypes)); return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr,
(result, tenantId, entityId) -> getLatestTimeseriesValuesCallback(result, user, entityId, keysStr, useStrictDataTypes));
} catch (Exception e) {
throw handleException(e);
}
} }
@ApiOperation(value = "Get time-series data (getTimeseries)", @ApiOperation(value = "Get time-series data (getTimeseries)",
@ -324,15 +349,19 @@ public class TelemetryController extends BaseController {
@RequestParam(name = "orderBy", defaultValue = "DESC") String orderBy, @RequestParam(name = "orderBy", defaultValue = "DESC") String orderBy,
@ApiParam(value = STRICT_DATA_TYPES_DESCRIPTION) @ApiParam(value = STRICT_DATA_TYPES_DESCRIPTION)
@RequestParam(name = "useStrictDataTypes", required = false, defaultValue = "false") Boolean useStrictDataTypes) throws ThingsboardException { @RequestParam(name = "useStrictDataTypes", required = false, defaultValue = "false") Boolean useStrictDataTypes) throws ThingsboardException {
return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr, try {
(result, tenantId, entityId) -> { return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr,
// If interval is 0, convert this to a NONE aggregation, which is probably what the user really wanted (result, tenantId, entityId) -> {
Aggregation agg = interval == 0L ? Aggregation.valueOf(Aggregation.NONE.name()) : Aggregation.valueOf(aggStr); // If interval is 0, convert this to a NONE aggregation, which is probably what the user really wanted
List<ReadTsKvQuery> queries = toKeysList(keys).stream().map(key -> new BaseReadTsKvQuery(key, startTs, endTs, interval, limit, agg, orderBy)) Aggregation agg = interval == 0L ? Aggregation.valueOf(Aggregation.NONE.name()) : Aggregation.valueOf(aggStr);
.collect(Collectors.toList()); List<ReadTsKvQuery> queries = toKeysList(keys).stream().map(key -> new BaseReadTsKvQuery(key, startTs, endTs, interval, limit, agg, orderBy))
.collect(Collectors.toList());
Futures.addCallback(tsService.findAll(tenantId, entityId, queries), getTsKvListCallback(result, useStrictDataTypes), MoreExecutors.directExecutor());
}); Futures.addCallback(tsService.findAll(tenantId, entityId, queries), getTsKvListCallback(result, useStrictDataTypes), MoreExecutors.directExecutor());
});
} catch (Exception e) {
throw handleException(e);
}
} }
@ApiOperation(value = "Save device attributes (saveDeviceAttributes)", @ApiOperation(value = "Save device attributes (saveDeviceAttributes)",
@ -356,8 +385,12 @@ public class TelemetryController extends BaseController {
@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION, required = true) @PathVariable("deviceId") String deviceIdStr, @ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION, required = true) @PathVariable("deviceId") String deviceIdStr,
@ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES, required = true) @PathVariable("scope") String scope, @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES, required = true) @PathVariable("scope") String scope,
@ApiParam(value = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException { @ApiParam(value = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE, deviceIdStr); try {
return saveAttributes(getTenantId(), entityId, scope, request); EntityId entityId = EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE, deviceIdStr);
return saveAttributes(getTenantId(), entityId, scope, request);
} catch (Exception e) {
throw handleException(e);
}
} }
@ApiOperation(value = "Save entity attributes (saveEntityAttributesV1)", @ApiOperation(value = "Save entity attributes (saveEntityAttributesV1)",
@ -380,8 +413,12 @@ public class TelemetryController extends BaseController {
@ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
@ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES) @PathVariable("scope") String scope, @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES) @PathVariable("scope") String scope,
@ApiParam(value = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException { @ApiParam(value = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); try {
return saveAttributes(getTenantId(), entityId, scope, request); EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
return saveAttributes(getTenantId(), entityId, scope, request);
} catch (Exception e) {
throw handleException(e);
}
} }
@ApiOperation(value = "Save entity attributes (saveEntityAttributesV2)", @ApiOperation(value = "Save entity attributes (saveEntityAttributesV2)",
@ -404,8 +441,12 @@ public class TelemetryController extends BaseController {
@ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
@ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES, required = true) @PathVariable("scope") String scope, @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES, required = true) @PathVariable("scope") String scope,
@ApiParam(value = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException { @ApiParam(value = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); try {
return saveAttributes(getTenantId(), entityId, scope, request); EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
return saveAttributes(getTenantId(), entityId, scope, request);
} catch (Exception e) {
throw handleException(e);
}
} }
@ -429,8 +470,12 @@ public class TelemetryController extends BaseController {
@ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
@ApiParam(value = TELEMETRY_SCOPE_DESCRIPTION, required = true, allowableValues = "ANY") @PathVariable("scope") String scope, @ApiParam(value = TELEMETRY_SCOPE_DESCRIPTION, required = true, allowableValues = "ANY") @PathVariable("scope") String scope,
@ApiParam(value = TELEMETRY_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody String requestBody) throws ThingsboardException { @ApiParam(value = TELEMETRY_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody String requestBody) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); try {
return saveTelemetry(getTenantId(), entityId, requestBody, 0L); EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
return saveTelemetry(getTenantId(), entityId, requestBody, 0L);
} catch (Exception e) {
throw handleException(e);
}
} }
@ApiOperation(value = "Save or update time-series data with TTL (saveEntityTelemetryWithTTL)", @ApiOperation(value = "Save or update time-series data with TTL (saveEntityTelemetryWithTTL)",
@ -455,8 +500,12 @@ public class TelemetryController extends BaseController {
@ApiParam(value = TELEMETRY_SCOPE_DESCRIPTION, required = true, allowableValues = "ANY") @PathVariable("scope") String scope, @ApiParam(value = TELEMETRY_SCOPE_DESCRIPTION, required = true, allowableValues = "ANY") @PathVariable("scope") String scope,
@ApiParam(value = "A long value representing TTL (Time to Live) parameter.", required = true) @PathVariable("ttl") Long ttl, @ApiParam(value = "A long value representing TTL (Time to Live) parameter.", required = true) @PathVariable("ttl") Long ttl,
@ApiParam(value = TELEMETRY_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody String requestBody) throws ThingsboardException { @ApiParam(value = TELEMETRY_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody String requestBody) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); try {
return saveTelemetry(getTenantId(), entityId, requestBody, ttl); EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
return saveTelemetry(getTenantId(), entityId, requestBody, ttl);
} catch (Exception e) {
throw handleException(e);
}
} }
@ApiOperation(value = "Delete entity time-series data (deleteEntityTimeseries)", @ApiOperation(value = "Delete entity time-series data (deleteEntityTimeseries)",
@ -489,8 +538,12 @@ public class TelemetryController extends BaseController {
@RequestParam(name = "endTs", required = false) Long endTs, @RequestParam(name = "endTs", required = false) Long endTs,
@ApiParam(value = "If the parameter is set to true, the latest telemetry will be rewritten in case that current latest value was removed, otherwise, in case that parameter is set to false the new latest value will not set.") @ApiParam(value = "If the parameter is set to true, the latest telemetry will be rewritten in case that current latest value was removed, otherwise, in case that parameter is set to false the new latest value will not set.")
@RequestParam(name = "rewriteLatestIfDeleted", defaultValue = "false") boolean rewriteLatestIfDeleted) throws ThingsboardException { @RequestParam(name = "rewriteLatestIfDeleted", defaultValue = "false") boolean rewriteLatestIfDeleted) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); try {
return deleteTimeseries(entityId, keysStr, deleteAllDataForKeys, startTs, endTs, rewriteLatestIfDeleted); EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
return deleteTimeseries(entityId, keysStr, deleteAllDataForKeys, startTs, endTs, rewriteLatestIfDeleted);
} catch (Exception e) {
throw handleException(e);
}
} }
private DeferredResult<ResponseEntity> deleteTimeseries(EntityId entityIdStr, String keysStr, boolean deleteAllDataForKeys, private DeferredResult<ResponseEntity> deleteTimeseries(EntityId entityIdStr, String keysStr, boolean deleteAllDataForKeys,
@ -556,8 +609,12 @@ public class TelemetryController extends BaseController {
@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION, required = true) @PathVariable(DEVICE_ID) String deviceIdStr, @ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION, required = true) @PathVariable(DEVICE_ID) String deviceIdStr,
@ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES, required = true) @PathVariable("scope") String scope, @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES, required = true) @PathVariable("scope") String scope,
@ApiParam(value = ATTRIBUTES_KEYS_DESCRIPTION, required = true) @RequestParam(name = "keys") String keysStr) throws ThingsboardException { @ApiParam(value = ATTRIBUTES_KEYS_DESCRIPTION, required = true) @RequestParam(name = "keys") String keysStr) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE, deviceIdStr); try {
return deleteAttributes(entityId, scope, keysStr); EntityId entityId = EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE, deviceIdStr);
return deleteAttributes(entityId, scope, keysStr);
} catch (Exception e) {
throw handleException(e);
}
} }
@ApiOperation(value = "Delete entity attributes (deleteEntityAttributes)", @ApiOperation(value = "Delete entity attributes (deleteEntityAttributes)",
@ -580,8 +637,12 @@ public class TelemetryController extends BaseController {
@ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
@ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, required = true, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES) @PathVariable("scope") String scope, @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, required = true, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES) @PathVariable("scope") String scope,
@ApiParam(value = ATTRIBUTES_KEYS_DESCRIPTION, required = true) @RequestParam(name = "keys") String keysStr) throws ThingsboardException { @ApiParam(value = ATTRIBUTES_KEYS_DESCRIPTION, required = true) @RequestParam(name = "keys") String keysStr) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); try {
return deleteAttributes(entityId, scope, keysStr); EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
return deleteAttributes(entityId, scope, keysStr);
} catch (Exception e) {
throw handleException(e);
}
} }
private DeferredResult<ResponseEntity> deleteAttributes(EntityId entityIdSrc, String scope, String keysStr) throws ThingsboardException { private DeferredResult<ResponseEntity> deleteAttributes(EntityId entityIdSrc, String scope, String keysStr) throws ThingsboardException {
@ -627,6 +688,7 @@ public class TelemetryController extends BaseController {
} }
if (json.isObject()) { if (json.isObject()) {
List<AttributeKvEntry> attributes = extractRequestAttributes(json); List<AttributeKvEntry> attributes = extractRequestAttributes(json);
attributes.forEach(ConstraintValidator::validateFields);
if (attributes.isEmpty()) { if (attributes.isEmpty()) {
return getImmediateDeferredResult("No attributes data found in request body!", HttpStatus.BAD_REQUEST); return getImmediateDeferredResult("No attributes data found in request body!", HttpStatus.BAD_REQUEST);
} }

4
application/src/main/java/org/thingsboard/server/exception/ThingsboardCredentialsExpiredResponse.java

@ -15,9 +15,12 @@
*/ */
package org.thingsboard.server.exception; package org.thingsboard.server.exception;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
@ApiModel
public class ThingsboardCredentialsExpiredResponse extends ThingsboardErrorResponse { public class ThingsboardCredentialsExpiredResponse extends ThingsboardErrorResponse {
private final String resetToken; private final String resetToken;
@ -31,6 +34,7 @@ public class ThingsboardCredentialsExpiredResponse extends ThingsboardErrorRespo
return new ThingsboardCredentialsExpiredResponse(message, resetToken); return new ThingsboardCredentialsExpiredResponse(message, resetToken);
} }
@ApiModelProperty(position = 5, value = "Password reset token", readOnly = true)
public String getResetToken() { public String getResetToken() {
return resetToken; return resetToken;
} }

20
application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponse.java

@ -15,11 +15,14 @@
*/ */
package org.thingsboard.server.exception; package org.thingsboard.server.exception;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import java.util.Date; import java.util.Date;
@ApiModel
public class ThingsboardErrorResponse { public class ThingsboardErrorResponse {
// HTTP Response Status Code // HTTP Response Status Code
private final HttpStatus status; private final HttpStatus status;
@ -43,18 +46,35 @@ public class ThingsboardErrorResponse {
return new ThingsboardErrorResponse(message, errorCode, status); return new ThingsboardErrorResponse(message, errorCode, status);
} }
@ApiModelProperty(position = 1, value = "HTTP Response Status Code", example = "401", readOnly = true)
public Integer getStatus() { public Integer getStatus() {
return status.value(); return status.value();
} }
@ApiModelProperty(position = 2, value = "Error message", example = "Authentication failed", readOnly = true)
public String getMessage() { public String getMessage() {
return message; return message;
} }
@ApiModelProperty(position = 3, value = "Platform error code:" +
"\n* `2` - General error (HTTP: 500 - Internal Server Error)" +
"\n\n* `10` - Authentication failed (HTTP: 401 - Unauthorized)" +
"\n\n* `11` - JWT token expired (HTTP: 401 - Unauthorized)" +
"\n\n* `15` - Credentials expired (HTTP: 401 - Unauthorized)" +
"\n\n* `20` - Permission denied (HTTP: 403 - Forbidden)" +
"\n\n* `30` - Invalid arguments (HTTP: 400 - Bad Request)" +
"\n\n* `31` - Bad request params (HTTP: 400 - Bad Request)" +
"\n\n* `32` - Item not found (HTTP: 404 - Not Found)" +
"\n\n* `33` - Too many requests (HTTP: 429 - Too Many Requests)" +
"\n\n* `34` - Too many updates (Too many updates over Websocket session)" +
"\n\n* `40` - Subscription violation (HTTP: 403 - Forbidden)",
example = "10", dataType = "integer",
readOnly = true)
public ThingsboardErrorCode getErrorCode() { public ThingsboardErrorCode getErrorCode() {
return errorCode; return errorCode;
} }
@ApiModelProperty(position = 4, value = "Timestamp", readOnly = true)
public Date getTimestamp() { public Date getTimestamp() {
return timestamp; return timestamp;
} }

84
application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java

@ -17,9 +17,13 @@ package org.thingsboard.server.exception;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.lang.Nullable;
import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException; import org.springframework.security.authentication.DisabledException;
@ -30,7 +34,9 @@ import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import org.springframework.web.util.WebUtils;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.msg.tools.TbRateLimitsException; import org.thingsboard.server.common.msg.tools.TbRateLimitsException;
@ -42,11 +48,49 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@Slf4j @Slf4j
@RestControllerAdvice @RestControllerAdvice
public class ThingsboardErrorResponseHandler extends ResponseEntityExceptionHandler implements AccessDeniedHandler { public class ThingsboardErrorResponseHandler extends ResponseEntityExceptionHandler implements AccessDeniedHandler {
private static final Map<HttpStatus, ThingsboardErrorCode> statusToErrorCodeMap = new HashMap<>();
static {
statusToErrorCodeMap.put(HttpStatus.BAD_REQUEST, ThingsboardErrorCode.BAD_REQUEST_PARAMS);
statusToErrorCodeMap.put(HttpStatus.UNAUTHORIZED, ThingsboardErrorCode.AUTHENTICATION);
statusToErrorCodeMap.put(HttpStatus.FORBIDDEN, ThingsboardErrorCode.PERMISSION_DENIED);
statusToErrorCodeMap.put(HttpStatus.NOT_FOUND, ThingsboardErrorCode.ITEM_NOT_FOUND);
statusToErrorCodeMap.put(HttpStatus.METHOD_NOT_ALLOWED, ThingsboardErrorCode.BAD_REQUEST_PARAMS);
statusToErrorCodeMap.put(HttpStatus.NOT_ACCEPTABLE, ThingsboardErrorCode.BAD_REQUEST_PARAMS);
statusToErrorCodeMap.put(HttpStatus.UNSUPPORTED_MEDIA_TYPE, ThingsboardErrorCode.BAD_REQUEST_PARAMS);
statusToErrorCodeMap.put(HttpStatus.TOO_MANY_REQUESTS, ThingsboardErrorCode.TOO_MANY_REQUESTS);
statusToErrorCodeMap.put(HttpStatus.INTERNAL_SERVER_ERROR, ThingsboardErrorCode.GENERAL);
statusToErrorCodeMap.put(HttpStatus.SERVICE_UNAVAILABLE, ThingsboardErrorCode.GENERAL);
}
private static final Map<ThingsboardErrorCode, HttpStatus> errorCodeToStatusMap = new HashMap<>();
static {
errorCodeToStatusMap.put(ThingsboardErrorCode.GENERAL, HttpStatus.INTERNAL_SERVER_ERROR);
errorCodeToStatusMap.put(ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED);
errorCodeToStatusMap.put(ThingsboardErrorCode.JWT_TOKEN_EXPIRED, HttpStatus.UNAUTHORIZED);
errorCodeToStatusMap.put(ThingsboardErrorCode.CREDENTIALS_EXPIRED, HttpStatus.UNAUTHORIZED);
errorCodeToStatusMap.put(ThingsboardErrorCode.PERMISSION_DENIED, HttpStatus.FORBIDDEN);
errorCodeToStatusMap.put(ThingsboardErrorCode.INVALID_ARGUMENTS, HttpStatus.BAD_REQUEST);
errorCodeToStatusMap.put(ThingsboardErrorCode.BAD_REQUEST_PARAMS, HttpStatus.BAD_REQUEST);
errorCodeToStatusMap.put(ThingsboardErrorCode.ITEM_NOT_FOUND, HttpStatus.NOT_FOUND);
errorCodeToStatusMap.put(ThingsboardErrorCode.TOO_MANY_REQUESTS, HttpStatus.TOO_MANY_REQUESTS);
errorCodeToStatusMap.put(ThingsboardErrorCode.TOO_MANY_UPDATES, HttpStatus.TOO_MANY_REQUESTS);
errorCodeToStatusMap.put(ThingsboardErrorCode.SUBSCRIPTION_VIOLATION, HttpStatus.FORBIDDEN);
}
private static ThingsboardErrorCode statusToErrorCode(HttpStatus status) {
return statusToErrorCodeMap.getOrDefault(status, ThingsboardErrorCode.GENERAL);
}
private static HttpStatus errorCodeToStatus(ThingsboardErrorCode errorCode) {
return errorCodeToStatusMap.getOrDefault(errorCode, HttpStatus.INTERNAL_SERVER_ERROR);
}
@Autowired @Autowired
private ObjectMapper mapper; private ObjectMapper mapper;
@ -95,36 +139,22 @@ public class ThingsboardErrorResponseHandler extends ResponseEntityExceptionHand
} }
} }
@NotNull
@Override
protected ResponseEntity<Object> handleExceptionInternal(
@NotNull Exception ex, @Nullable Object body,
@NotNull HttpHeaders headers, @NotNull HttpStatus status,
@NotNull WebRequest request) {
if (HttpStatus.INTERNAL_SERVER_ERROR.equals(status)) {
request.setAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE, ex, WebRequest.SCOPE_REQUEST);
}
ThingsboardErrorCode errorCode = statusToErrorCode(status);
return new ResponseEntity<>(ThingsboardErrorResponse.of(ex.getMessage(), errorCode, status), headers, status);
}
private void handleThingsboardException(ThingsboardException thingsboardException, HttpServletResponse response) throws IOException { private void handleThingsboardException(ThingsboardException thingsboardException, HttpServletResponse response) throws IOException {
ThingsboardErrorCode errorCode = thingsboardException.getErrorCode(); ThingsboardErrorCode errorCode = thingsboardException.getErrorCode();
HttpStatus status; HttpStatus status = errorCodeToStatus(errorCode);
switch (errorCode) {
case AUTHENTICATION:
status = HttpStatus.UNAUTHORIZED;
break;
case PERMISSION_DENIED:
status = HttpStatus.FORBIDDEN;
break;
case INVALID_ARGUMENTS:
status = HttpStatus.BAD_REQUEST;
break;
case ITEM_NOT_FOUND:
status = HttpStatus.NOT_FOUND;
break;
case BAD_REQUEST_PARAMS:
status = HttpStatus.BAD_REQUEST;
break;
case GENERAL:
status = HttpStatus.INTERNAL_SERVER_ERROR;
break;
default:
status = HttpStatus.INTERNAL_SERVER_ERROR;
break;
}
response.setStatus(status.value()); response.setStatus(status.value());
mapper.writeValue(response.getWriter(), ThingsboardErrorResponse.of(thingsboardException.getMessage(), errorCode, status)); mapper.writeValue(response.getWriter(), ThingsboardErrorResponse.of(thingsboardException.getMessage(), errorCode, status));
} }

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

@ -31,6 +31,7 @@ import org.thingsboard.server.service.install.TsDatabaseSchemaService;
import org.thingsboard.server.service.install.TsLatestDatabaseSchemaService; import org.thingsboard.server.service.install.TsLatestDatabaseSchemaService;
import org.thingsboard.server.service.install.migrate.EntitiesMigrateService; import org.thingsboard.server.service.install.migrate.EntitiesMigrateService;
import org.thingsboard.server.service.install.migrate.TsLatestMigrateService; import org.thingsboard.server.service.install.migrate.TsLatestMigrateService;
import org.thingsboard.server.service.install.update.CacheCleanupService;
import org.thingsboard.server.service.install.update.DataUpdateService; import org.thingsboard.server.service.install.update.DataUpdateService;
@Service @Service
@ -74,6 +75,9 @@ public class ThingsboardInstallService {
@Autowired @Autowired
private DataUpdateService dataUpdateService; private DataUpdateService dataUpdateService;
@Autowired
private CacheCleanupService cacheCleanupService;
@Autowired(required = false) @Autowired(required = false)
private EntitiesMigrateService entitiesMigrateService; private EntitiesMigrateService entitiesMigrateService;
@ -85,6 +89,8 @@ public class ThingsboardInstallService {
if (isUpgrade) { if (isUpgrade) {
log.info("Starting ThingsBoard Upgrade from version {} ...", upgradeFromVersion); log.info("Starting ThingsBoard Upgrade from version {} ...", upgradeFromVersion);
cacheCleanupService.clearCache(upgradeFromVersion);
if ("2.5.0-cassandra".equals(upgradeFromVersion)) { if ("2.5.0-cassandra".equals(upgradeFromVersion)) {
log.info("Migrating ThingsBoard entities data from cassandra to SQL database ..."); log.info("Migrating ThingsBoard entities data from cassandra to SQL database ...");
entitiesMigrateService.migrate(); entitiesMigrateService.migrate();
@ -207,6 +213,9 @@ public class ThingsboardInstallService {
log.info("Updating system data..."); log.info("Updating system data...");
systemDataLoaderService.updateSystemWidgets(); systemDataLoaderService.updateSystemWidgets();
break; break;
//TODO update CacheCleanupService on the next version upgrade
default: default:
throw new RuntimeException("Unable to upgrade ThingsBoard, unsupported fromVersion: " + upgradeFromVersion); throw new RuntimeException("Unable to upgrade ThingsBoard, unsupported fromVersion: " + upgradeFromVersion);

73
application/src/main/java/org/thingsboard/server/service/asset/AssetBulkImportService.java

@ -17,78 +17,69 @@ package org.thingsboard.server.service.asset;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode; import com.fasterxml.jackson.databind.node.TextNode;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.action.EntityActionService;
import org.thingsboard.server.service.importing.AbstractBulkImportService; import org.thingsboard.server.service.importing.AbstractBulkImportService;
import org.thingsboard.server.service.importing.BulkImportColumnType; import org.thingsboard.server.service.importing.BulkImportColumnType;
import org.thingsboard.server.service.importing.BulkImportRequest;
import org.thingsboard.server.service.importing.ImportedEntityInfo;
import org.thingsboard.server.service.security.AccessValidator;
import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.AccessControlService;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
@Service @Service
@TbCoreComponent @TbCoreComponent
@RequiredArgsConstructor
public class AssetBulkImportService extends AbstractBulkImportService<Asset> { public class AssetBulkImportService extends AbstractBulkImportService<Asset> {
private final AssetService assetService; private final AssetService assetService;
public AssetBulkImportService(TelemetrySubscriptionService tsSubscriptionService, TbTenantProfileCache tenantProfileCache,
AccessControlService accessControlService, AccessValidator accessValidator,
EntityActionService entityActionService, TbClusterService clusterService, AssetService assetService) {
super(tsSubscriptionService, tenantProfileCache, accessControlService, accessValidator, entityActionService, clusterService);
this.assetService = assetService;
}
@Override @Override
protected ImportedEntityInfo<Asset> saveEntity(BulkImportRequest importRequest, Map<BulkImportColumnType, String> fields, SecurityUser user) { protected void setEntityFields(Asset entity, Map<BulkImportColumnType, String> fields) {
ImportedEntityInfo<Asset> importedEntityInfo = new ImportedEntityInfo<>(); ObjectNode additionalInfo = (ObjectNode) Optional.ofNullable(entity.getAdditionalInfo()).orElseGet(JacksonUtil::newObjectNode);
Asset asset = new Asset();
asset.setTenantId(user.getTenantId());
setAssetFields(asset, fields);
Asset existingAsset = assetService.findAssetByTenantIdAndName(user.getTenantId(), asset.getName());
if (existingAsset != null && importRequest.getMapping().getUpdate()) {
importedEntityInfo.setOldEntity(new Asset(existingAsset));
importedEntityInfo.setUpdated(true);
existingAsset.update(asset);
asset = existingAsset;
}
asset = assetService.saveAsset(asset);
importedEntityInfo.setEntity(asset);
return importedEntityInfo;
}
private void setAssetFields(Asset asset, Map<BulkImportColumnType, String> fields) {
ObjectNode additionalInfo = (ObjectNode) Optional.ofNullable(asset.getAdditionalInfo()).orElseGet(JacksonUtil::newObjectNode);
fields.forEach((columnType, value) -> { fields.forEach((columnType, value) -> {
switch (columnType) { switch (columnType) {
case NAME: case NAME:
asset.setName(value); entity.setName(value);
break; break;
case TYPE: case TYPE:
asset.setType(value); entity.setType(value);
break; break;
case LABEL: case LABEL:
asset.setLabel(value); entity.setLabel(value);
break; break;
case DESCRIPTION: case DESCRIPTION:
additionalInfo.set("description", new TextNode(value)); additionalInfo.set("description", new TextNode(value));
break; break;
} }
}); });
asset.setAdditionalInfo(additionalInfo); entity.setAdditionalInfo(additionalInfo);
}
@Override
protected Asset saveEntity(Asset entity, Map<BulkImportColumnType, String> fields) {
return assetService.saveAsset(entity);
}
@Override
protected Asset findOrCreateEntity(TenantId tenantId, String name) {
return Optional.ofNullable(assetService.findAssetByTenantIdAndName(tenantId, name))
.orElseGet(Asset::new);
}
@Override
protected void setOwners(Asset entity, SecurityUser user) {
entity.setTenantId(user.getTenantId());
entity.setCustomerId(user.getCustomerId());
}
@Override
protected EntityType getEntityType() {
return EntityType.ASSET;
} }
} }

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

@ -18,18 +18,19 @@ package org.thingsboard.server.service.device;
import com.fasterxml.jackson.databind.node.BooleanNode; import com.fasterxml.jackson.databind.node.BooleanNode;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode; import com.fasterxml.jackson.databind.node.TextNode;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.DeviceProfileProvisionType;
import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceProfileType;
import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredential; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredential;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode;
@ -45,17 +46,10 @@ import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException; import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.action.EntityActionService;
import org.thingsboard.server.service.importing.AbstractBulkImportService; import org.thingsboard.server.service.importing.AbstractBulkImportService;
import org.thingsboard.server.service.importing.BulkImportColumnType; import org.thingsboard.server.service.importing.BulkImportColumnType;
import org.thingsboard.server.service.importing.BulkImportRequest;
import org.thingsboard.server.service.importing.ImportedEntityInfo;
import org.thingsboard.server.service.security.AccessValidator;
import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.AccessControlService;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import java.util.Collection; import java.util.Collection;
import java.util.EnumSet; import java.util.EnumSet;
@ -68,6 +62,7 @@ import java.util.concurrent.locks.ReentrantLock;
@Service @Service
@TbCoreComponent @TbCoreComponent
@RequiredArgsConstructor
public class DeviceBulkImportService extends AbstractBulkImportService<Device> { public class DeviceBulkImportService extends AbstractBulkImportService<Device> {
protected final DeviceService deviceService; protected final DeviceService deviceService;
protected final DeviceCredentialsService deviceCredentialsService; protected final DeviceCredentialsService deviceCredentialsService;
@ -75,33 +70,33 @@ public class DeviceBulkImportService extends AbstractBulkImportService<Device> {
private final Lock findOrCreateDeviceProfileLock = new ReentrantLock(); private final Lock findOrCreateDeviceProfileLock = new ReentrantLock();
public DeviceBulkImportService(TelemetrySubscriptionService tsSubscriptionService, TbTenantProfileCache tenantProfileCache, @Override
AccessControlService accessControlService, AccessValidator accessValidator, protected void setEntityFields(Device entity, Map<BulkImportColumnType, String> fields) {
EntityActionService entityActionService, TbClusterService clusterService, ObjectNode additionalInfo = (ObjectNode) Optional.ofNullable(entity.getAdditionalInfo()).orElseGet(JacksonUtil::newObjectNode);
DeviceService deviceService, DeviceCredentialsService deviceCredentialsService, fields.forEach((columnType, value) -> {
DeviceProfileService deviceProfileService) { switch (columnType) {
super(tsSubscriptionService, tenantProfileCache, accessControlService, accessValidator, entityActionService, clusterService); case NAME:
this.deviceService = deviceService; entity.setName(value);
this.deviceCredentialsService = deviceCredentialsService; break;
this.deviceProfileService = deviceProfileService; case TYPE:
entity.setType(value);
break;
case LABEL:
entity.setLabel(value);
break;
case DESCRIPTION:
additionalInfo.set("description", new TextNode(value));
break;
case IS_GATEWAY:
additionalInfo.set("gateway", BooleanNode.valueOf(Boolean.parseBoolean(value)));
break;
}
entity.setAdditionalInfo(additionalInfo);
});
} }
@Override @Override
protected ImportedEntityInfo<Device> saveEntity(BulkImportRequest importRequest, Map<BulkImportColumnType, String> fields, SecurityUser user) { protected Device saveEntity(Device entity, Map<BulkImportColumnType, String> fields) {
ImportedEntityInfo<Device> importedEntityInfo = new ImportedEntityInfo<>();
Device device = new Device();
device.setTenantId(user.getTenantId());
setDeviceFields(device, fields);
Device existingDevice = deviceService.findDeviceByTenantIdAndName(user.getTenantId(), device.getName());
if (existingDevice != null && importRequest.getMapping().getUpdate()) {
importedEntityInfo.setOldEntity(new Device(existingDevice));
importedEntityInfo.setUpdated(true);
existingDevice.updateDevice(device);
device = existingDevice;
}
DeviceCredentials deviceCredentials; DeviceCredentials deviceCredentials;
try { try {
deviceCredentials = createDeviceCredentials(fields); deviceCredentials = createDeviceCredentials(fields);
@ -112,42 +107,27 @@ public class DeviceBulkImportService extends AbstractBulkImportService<Device> {
DeviceProfile deviceProfile; DeviceProfile deviceProfile;
if (deviceCredentials.getCredentialsType() == DeviceCredentialsType.LWM2M_CREDENTIALS) { if (deviceCredentials.getCredentialsType() == DeviceCredentialsType.LWM2M_CREDENTIALS) {
deviceProfile = setUpLwM2mDeviceProfile(user.getTenantId(), device); deviceProfile = setUpLwM2mDeviceProfile(entity.getTenantId(), entity);
} else if (StringUtils.isNotEmpty(device.getType())) { } else if (StringUtils.isNotEmpty(entity.getType())) {
deviceProfile = deviceProfileService.findOrCreateDeviceProfile(user.getTenantId(), device.getType()); deviceProfile = deviceProfileService.findOrCreateDeviceProfile(entity.getTenantId(), entity.getType());
} else { } else {
deviceProfile = deviceProfileService.findDefaultDeviceProfile(user.getTenantId()); deviceProfile = deviceProfileService.findDefaultDeviceProfile(entity.getTenantId());
} }
device.setDeviceProfileId(deviceProfile.getId()); entity.setDeviceProfileId(deviceProfile.getId());
device = deviceService.saveDeviceWithCredentials(device, deviceCredentials); return deviceService.saveDeviceWithCredentials(entity, deviceCredentials);
}
importedEntityInfo.setEntity(device); @Override
return importedEntityInfo; protected Device findOrCreateEntity(TenantId tenantId, String name) {
return Optional.ofNullable(deviceService.findDeviceByTenantIdAndName(tenantId, name))
.orElseGet(Device::new);
} }
private void setDeviceFields(Device device, Map<BulkImportColumnType, String> fields) { @Override
ObjectNode additionalInfo = (ObjectNode) Optional.ofNullable(device.getAdditionalInfo()).orElseGet(JacksonUtil::newObjectNode); protected void setOwners(Device entity, SecurityUser user) {
fields.forEach((columnType, value) -> { entity.setTenantId(user.getTenantId());
switch (columnType) { entity.setCustomerId(user.getCustomerId());
case NAME:
device.setName(value);
break;
case TYPE:
device.setType(value);
break;
case LABEL:
device.setLabel(value);
break;
case DESCRIPTION:
additionalInfo.set("description", new TextNode(value));
break;
case IS_GATEWAY:
additionalInfo.set("gateway", BooleanNode.valueOf(Boolean.parseBoolean(value)));
break;
}
device.setAdditionalInfo(additionalInfo);
});
} }
@SneakyThrows @SneakyThrows
@ -273,4 +253,9 @@ public class DeviceBulkImportService extends AbstractBulkImportService<Device> {
} }
} }
@Override
protected EntityType getEntityType() {
return EntityType.DEVICE;
}
} }

81
application/src/main/java/org/thingsboard/server/service/edge/EdgeBulkImportService.java

@ -17,90 +17,81 @@ package org.thingsboard.server.service.edge;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode; import com.fasterxml.jackson.databind.node.TextNode;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.action.EntityActionService;
import org.thingsboard.server.service.importing.AbstractBulkImportService; import org.thingsboard.server.service.importing.AbstractBulkImportService;
import org.thingsboard.server.service.importing.BulkImportColumnType; import org.thingsboard.server.service.importing.BulkImportColumnType;
import org.thingsboard.server.service.importing.BulkImportRequest;
import org.thingsboard.server.service.importing.ImportedEntityInfo;
import org.thingsboard.server.service.security.AccessValidator;
import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.AccessControlService;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
@Service @Service
@TbCoreComponent @TbCoreComponent
@RequiredArgsConstructor
public class EdgeBulkImportService extends AbstractBulkImportService<Edge> { public class EdgeBulkImportService extends AbstractBulkImportService<Edge> {
private final EdgeService edgeService; private final EdgeService edgeService;
public EdgeBulkImportService(TelemetrySubscriptionService tsSubscriptionService, TbTenantProfileCache tenantProfileCache,
AccessControlService accessControlService, AccessValidator accessValidator,
EntityActionService entityActionService, TbClusterService clusterService, EdgeService edgeService) {
super(tsSubscriptionService, tenantProfileCache, accessControlService, accessValidator, entityActionService, clusterService);
this.edgeService = edgeService;
}
@Override @Override
protected ImportedEntityInfo<Edge> saveEntity(BulkImportRequest importRequest, Map<BulkImportColumnType, String> fields, SecurityUser user) { protected void setEntityFields(Edge entity, Map<BulkImportColumnType, String> fields) {
ImportedEntityInfo<Edge> importedEntityInfo = new ImportedEntityInfo<>(); ObjectNode additionalInfo = (ObjectNode) Optional.ofNullable(entity.getAdditionalInfo()).orElseGet(JacksonUtil::newObjectNode);
Edge edge = new Edge();
edge.setTenantId(user.getTenantId());
setEdgeFields(edge, fields);
Edge existingEdge = edgeService.findEdgeByTenantIdAndName(user.getTenantId(), edge.getName());
if (existingEdge != null && importRequest.getMapping().getUpdate()) {
importedEntityInfo.setOldEntity(new Edge(existingEdge));
importedEntityInfo.setUpdated(true);
existingEdge.update(edge);
edge = existingEdge;
}
edge = edgeService.saveEdge(edge, true);
importedEntityInfo.setEntity(edge);
return importedEntityInfo;
}
private void setEdgeFields(Edge edge, Map<BulkImportColumnType, String> fields) {
ObjectNode additionalInfo = (ObjectNode) Optional.ofNullable(edge.getAdditionalInfo()).orElseGet(JacksonUtil::newObjectNode);
fields.forEach((columnType, value) -> { fields.forEach((columnType, value) -> {
switch (columnType) { switch (columnType) {
case NAME: case NAME:
edge.setName(value); entity.setName(value);
break; break;
case TYPE: case TYPE:
edge.setType(value); entity.setType(value);
break; break;
case LABEL: case LABEL:
edge.setLabel(value); entity.setLabel(value);
break; break;
case DESCRIPTION: case DESCRIPTION:
additionalInfo.set("description", new TextNode(value)); additionalInfo.set("description", new TextNode(value));
break; break;
case EDGE_LICENSE_KEY: case EDGE_LICENSE_KEY:
edge.setEdgeLicenseKey(value); entity.setEdgeLicenseKey(value);
break; break;
case CLOUD_ENDPOINT: case CLOUD_ENDPOINT:
edge.setCloudEndpoint(value); entity.setCloudEndpoint(value);
break; break;
case ROUTING_KEY: case ROUTING_KEY:
edge.setRoutingKey(value); entity.setRoutingKey(value);
break; break;
case SECRET: case SECRET:
edge.setSecret(value); entity.setSecret(value);
break; break;
} }
}); });
edge.setAdditionalInfo(additionalInfo); entity.setAdditionalInfo(additionalInfo);
}
@Override
protected Edge saveEntity(Edge entity, Map<BulkImportColumnType, String> fields) {
return edgeService.saveEdge(entity, true);
}
@Override
protected Edge findOrCreateEntity(TenantId tenantId, String name) {
return Optional.ofNullable(edgeService.findEdgeByTenantIdAndName(tenantId, name))
.orElseGet(Edge::new);
}
@Override
protected void setOwners(Edge entity, SecurityUser user) {
entity.setTenantId(user.getTenantId());
entity.setCustomerId(user.getCustomerId());
}
@Override
protected EntityType getEntityType() {
return EntityType.EDGE;
} }
} }

67
application/src/main/java/org/thingsboard/server/service/importing/AbstractBulkImportService.java

@ -19,19 +19,21 @@ import com.google.common.util.concurrent.FutureCallback;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive; import com.google.gson.JsonPrimitive;
import lombok.Data; import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.thingsboard.common.util.DonAsynchron; import org.thingsboard.common.util.DonAsynchron;
import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.HasId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UUIDBased; import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
@ -47,6 +49,7 @@ import org.thingsboard.server.service.security.AccessValidator;
import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.AccessControlService; import org.thingsboard.server.service.security.permission.AccessControlService;
import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import org.thingsboard.server.utils.CsvUtils; import org.thingsboard.server.utils.CsvUtils;
import org.thingsboard.server.utils.TypeCastUtil; import org.thingsboard.server.utils.TypeCastUtil;
@ -68,14 +71,17 @@ import java.util.function.Consumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@RequiredArgsConstructor public abstract class AbstractBulkImportService<E extends HasId<? extends EntityId> & HasTenantId> {
public abstract class AbstractBulkImportService<E extends BaseData<? extends EntityId>> { @Autowired
protected final TelemetrySubscriptionService tsSubscriptionService; private TelemetrySubscriptionService tsSubscriptionService;
protected final TbTenantProfileCache tenantProfileCache; @Autowired
protected final AccessControlService accessControlService; private TbTenantProfileCache tenantProfileCache;
protected final AccessValidator accessValidator; @Autowired
protected final EntityActionService entityActionService; private AccessControlService accessControlService;
protected final TbClusterService clusterService; @Autowired
private AccessValidator accessValidator;
@Autowired
private EntityActionService entityActionService;
private static ThreadPoolExecutor executor; private static ThreadPoolExecutor executor;
@ -100,7 +106,7 @@ public abstract class AbstractBulkImportService<E extends BaseData<? extends Ent
entitiesData.forEach(entityData -> DonAsynchron.submit(() -> { entitiesData.forEach(entityData -> DonAsynchron.submit(() -> {
SecurityContextHolder.setContext(securityContext); SecurityContextHolder.setContext(securityContext);
ImportedEntityInfo<E> importedEntityInfo = saveEntity(request, entityData.getFields(), user); ImportedEntityInfo<E> importedEntityInfo = saveEntity(entityData.getFields(), user);
E entity = importedEntityInfo.getEntity(); E entity = importedEntityInfo.getEntity();
onEntityImported.accept(importedEntityInfo); onEntityImported.accept(importedEntityInfo);
@ -127,12 +133,39 @@ public abstract class AbstractBulkImportService<E extends BaseData<? extends Ent
return result; return result;
} }
protected abstract ImportedEntityInfo<E> saveEntity(BulkImportRequest importRequest, Map<BulkImportColumnType, String> fields, SecurityUser user); @SneakyThrows
private ImportedEntityInfo<E> saveEntity(Map<BulkImportColumnType, String> fields, SecurityUser user) {
ImportedEntityInfo<E> importedEntityInfo = new ImportedEntityInfo<>();
E entity = findOrCreateEntity(user.getTenantId(), fields.get(BulkImportColumnType.NAME));
if (entity.getId() != null) {
importedEntityInfo.setOldEntity((E) entity.getClass().getConstructor(entity.getClass()).newInstance(entity));
importedEntityInfo.setUpdated(true);
} else {
setOwners(entity, user);
}
setEntityFields(entity, fields);
accessControlService.checkPermission(user, Resource.of(getEntityType()), Operation.WRITE, entity.getId(), entity);
E savedEntity = saveEntity(entity, fields);
importedEntityInfo.setEntity(savedEntity);
return importedEntityInfo;
}
protected abstract E findOrCreateEntity(TenantId tenantId, String name);
protected abstract void setOwners(E entity, SecurityUser user);
protected abstract void setEntityFields(E entity, Map<BulkImportColumnType, String> fields);
protected abstract E saveEntity(E entity, Map<BulkImportColumnType, String> fields);
protected abstract EntityType getEntityType();
/*
* Attributes' values are firstly added to JsonObject in order to then make some type cast,
* because we get all values as strings from CSV
* */
private void saveKvs(SecurityUser user, E entity, Map<ColumnMapping, ParsedValue> data) { private void saveKvs(SecurityUser user, E entity, Map<ColumnMapping, ParsedValue> data) {
Arrays.stream(BulkImportColumnType.values()) Arrays.stream(BulkImportColumnType.values())
.filter(BulkImportColumnType::isKv) .filter(BulkImportColumnType::isKv)

18
ui-ngx/src/app/modules/home/components/widget/legend-config-panel.component.scss → application/src/main/java/org/thingsboard/server/service/install/update/CacheCleanupService.java

@ -13,20 +13,10 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
:host { package org.thingsboard.server.service.install.update;
width: 100%;
height: 100%;
form,
fieldset {
height: 100%;
}
.mat-content { public interface CacheCleanupService {
overflow: hidden;
background-color: #fff; void clearCache(String fromVersion) throws Exception;
}
.mat-padding {
padding: 16px;
}
} }

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

@ -0,0 +1,75 @@
/**
* Copyright © 2016-2021 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.install.update;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import java.util.Objects;
@Service
@Profile("install")
@Slf4j
public class DefaultCacheCleanupService implements CacheCleanupService {
@Autowired
CacheManager cacheManager;
/**
* Cleanup caches that can not deserialize anymore due to schema upgrade or data update using sql scripts.
* Refer to SqlDatabaseUpgradeService and /data/upgrage/*.sql
* to discover which tables were changed
* */
@Override
public void clearCache(String fromVersion) throws Exception {
switch (fromVersion) {
case "3.0.1":
log.info("Clear cache to upgrade from version 3.0.1 to 3.1.0 ...");
clearAllCaches();
//do not break to show explicit calls for next versions
case "3.1.1":
log.info("Clear cache to upgrade from version 3.1.1 to 3.2.0 ...");
clearCacheByName("devices");
clearCacheByName("deviceProfiles");
clearCacheByName("tenantProfiles");
case "3.2.2":
log.info("Clear cache to upgrade from version 3.2.2 to 3.3.0 ...");
clearCacheByName("devices");
clearCacheByName("deviceProfiles");
clearCacheByName("tenantProfiles");
clearCacheByName("relations");
break;
default:
throw new RuntimeException("Unable to update cache, unsupported fromVersion: " + fromVersion);
}
}
void clearAllCaches() {
cacheManager.getCacheNames().forEach(this::clearCacheByName);
}
void clearCacheByName(final String cacheName) {
Cache cache = cacheManager.getCache(cacheName);
Objects.requireNonNull(cache, "Cache does not exist for name " + cacheName);
cache.clear();
}
}

65
application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java

@ -63,6 +63,7 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isBlank;
@ -144,33 +145,38 @@ public class DefaultDataUpdateService implements DataUpdateService {
@Override @Override
protected void updateEntity(DeviceProfileEntity deviceProfile) { protected void updateEntity(DeviceProfileEntity deviceProfile) {
if (deviceProfile.getProfileData().has("alarms") && if (convertDeviceProfileForVersion330(deviceProfile.getProfileData())) {
!deviceProfile.getProfileData().get("alarms").isNull()) { deviceProfileRepository.save(deviceProfile);
boolean isUpdated = false; }
JsonNode alarms = deviceProfile.getProfileData().get("alarms"); }
for (JsonNode alarm : alarms) { };
if (alarm.has("createRules")) {
JsonNode createRules = alarm.get("createRules"); boolean convertDeviceProfileForVersion330(JsonNode profileData) {
for (AlarmSeverity severity : AlarmSeverity.values()) { boolean isUpdated = false;
if (createRules.has(severity.name())) { if (profileData.has("alarms") && !profileData.get("alarms").isNull()) {
JsonNode spec = createRules.get(severity.name()).get("condition").get("spec"); JsonNode alarms = profileData.get("alarms");
if (convertDeviceProfileAlarmRulesForVersion330(spec)) for (JsonNode alarm : alarms) {
isUpdated = true; if (alarm.has("createRules")) {
} JsonNode createRules = alarm.get("createRules");
} for (AlarmSeverity severity : AlarmSeverity.values()) {
} if (createRules.has(severity.name())) {
if (alarm.has("clearRule") && !alarm.get("clearRule").isNull()) { JsonNode spec = createRules.get(severity.name()).get("condition").get("spec");
JsonNode spec = alarm.get("clearRule").get("condition").get("spec"); if (convertDeviceProfileAlarmRulesForVersion330(spec)) {
if (convertDeviceProfileAlarmRulesForVersion330(spec)) isUpdated = true;
isUpdated = true;
} }
} }
if (isUpdated) {
deviceProfileRepository.save(deviceProfile);
}
} }
} }
}; if (alarm.has("clearRule") && !alarm.get("clearRule").isNull()) {
JsonNode spec = alarm.get("clearRule").get("condition").get("spec");
if (convertDeviceProfileAlarmRulesForVersion330(spec)) {
isUpdated = true;
}
}
}
}
return isUpdated;
}
private final PaginatedUpdater<String, Tenant> tenantsDefaultRuleChainUpdater = private final PaginatedUpdater<String, Tenant> tenantsDefaultRuleChainUpdater =
new PaginatedUpdater<>() { new PaginatedUpdater<>() {
@ -380,6 +386,8 @@ public class DefaultDataUpdateService implements DataUpdateService {
private final PaginatedUpdater<String, Tenant> tenantsAlarmsCustomerUpdater = private final PaginatedUpdater<String, Tenant> tenantsAlarmsCustomerUpdater =
new PaginatedUpdater<>() { new PaginatedUpdater<>() {
final AtomicLong processed = new AtomicLong();
@Override @Override
protected String getName() { protected String getName() {
return "Tenants alarms customer updater"; return "Tenants alarms customer updater";
@ -397,12 +405,12 @@ public class DefaultDataUpdateService implements DataUpdateService {
@Override @Override
protected void updateEntity(Tenant tenant) { protected void updateEntity(Tenant tenant) {
updateTenantAlarmsCustomer(tenant.getId()); updateTenantAlarmsCustomer(tenant.getId(), getName(), processed);
} }
}; };
private void updateTenantAlarmsCustomer(TenantId tenantId) { private void updateTenantAlarmsCustomer(TenantId tenantId, String name, AtomicLong processed) {
AlarmQuery alarmQuery = new AlarmQuery(null, new TimePageLink(100), null, null, false); AlarmQuery alarmQuery = new AlarmQuery(null, new TimePageLink(1000), null, null, false);
PageData<AlarmInfo> alarms = alarmDao.findAlarms(tenantId, alarmQuery); PageData<AlarmInfo> alarms = alarmDao.findAlarms(tenantId, alarmQuery);
boolean hasNext = true; boolean hasNext = true;
while (hasNext) { while (hasNext) {
@ -411,6 +419,9 @@ public class DefaultDataUpdateService implements DataUpdateService {
alarm.setCustomerId(entityService.fetchEntityCustomerId(tenantId, alarm.getOriginator())); alarm.setCustomerId(entityService.fetchEntityCustomerId(tenantId, alarm.getOriginator()));
alarmDao.save(tenantId, alarm); alarmDao.save(tenantId, alarm);
} }
if (processed.incrementAndGet() % 1000 == 0) {
log.info("{}: {} alarms processed so far...", name, processed);
}
} }
if (alarms.hasNext()) { if (alarms.hasNext()) {
alarmQuery.setPageLink(alarmQuery.getPageLink().nextPageLink()); alarmQuery.setPageLink(alarmQuery.getPageLink().nextPageLink());
@ -421,7 +432,7 @@ public class DefaultDataUpdateService implements DataUpdateService {
} }
} }
private boolean convertDeviceProfileAlarmRulesForVersion330(JsonNode spec) { boolean convertDeviceProfileAlarmRulesForVersion330(JsonNode spec) {
if (spec != null) { if (spec != null) {
if (spec.has("type") && spec.get("type").asText().equals("DURATION")) { if (spec.has("type") && spec.get("type").asText().equals("DURATION")) {
if (spec.has("value")) { if (spec.has("value")) {

1
application/src/main/java/org/thingsboard/server/service/install/update/PaginatedUpdater.java

@ -28,6 +28,7 @@ public abstract class PaginatedUpdater<I, D> {
private int updated = 0; private int updated = 0;
public void updateEntities(I id) { public void updateEntities(I id) {
log.info("{}: started...", getName());
updated = 0; updated = 0;
PageLink pageLink = new PageLink(DEFAULT_LIMIT); PageLink pageLink = new PageLink(DEFAULT_LIMIT);
boolean hasNext = true; boolean hasNext = true;

11
application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServiceImpl.java

@ -18,15 +18,13 @@ package org.thingsboard.server.service.lwm2m;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.Base64;
import org.eclipse.leshan.core.util.Hex;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.lwm2m.ServerSecurityConfig; import org.thingsboard.server.common.data.lwm2m.ServerSecurityConfig;
import org.thingsboard.server.common.transport.config.ssl.SslCredentials;
import org.thingsboard.server.transport.lwm2m.config.LwM2MSecureServerConfig; import org.thingsboard.server.transport.lwm2m.config.LwM2MSecureServerConfig;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
import java.security.KeyStore;
import java.security.cert.X509Certificate;
@Slf4j @Slf4j
@Service @Service
@ -62,10 +60,9 @@ public class LwM2MServiceImpl implements LwM2MService {
private byte[] getPublicKey(LwM2MSecureServerConfig config) { private byte[] getPublicKey(LwM2MSecureServerConfig config) {
try { try {
KeyStore keyStore = serverConfig.getKeyStoreValue(); SslCredentials sslCredentials = config.getSslCredentials();
if (keyStore != null) { if (sslCredentials != null) {
X509Certificate serverCertificate = (X509Certificate) serverConfig.getKeyStoreValue().getCertificate(config.getCertificateAlias()); return sslCredentials.getPublicKey().getEncoded();
return serverCertificate.getPublicKey().getEncoded();
} }
} catch (Exception e) { } catch (Exception e) {
log.trace("Failed to fetch public key from key store!", e); log.trace("Failed to fetch public key from key store!", e);

15
application/src/main/java/org/thingsboard/server/service/queue/processing/TbRuleEngineProcessingStrategyFactory.java

@ -17,6 +17,7 @@ package org.thingsboard.server.service.queue.processing;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.queue.TbMsgCallback; import org.thingsboard.server.common.msg.queue.TbMsgCallback;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
@ -77,6 +78,7 @@ public class TbRuleEngineProcessingStrategyFactory {
@Override @Override
public TbRuleEngineProcessingDecision analyze(TbRuleEngineProcessingResult result) { public TbRuleEngineProcessingDecision analyze(TbRuleEngineProcessingResult result) {
if (result.isSuccess()) { if (result.isSuccess()) {
log.trace("[{}] The result of the msg pack processing is successful, going to proceed with processing of the following msgs", queueName);
return new TbRuleEngineProcessingDecision(true, null); return new TbRuleEngineProcessingDecision(true, null);
} else { } else {
if (retryCount == 0) { if (retryCount == 0) {
@ -91,15 +93,28 @@ public class TbRuleEngineProcessingStrategyFactory {
log.debug("[{}] Skip reprocess of the rule engine pack due to max allowed failure percentage", queueName); log.debug("[{}] Skip reprocess of the rule engine pack due to max allowed failure percentage", queueName);
return new TbRuleEngineProcessingDecision(true, null); return new TbRuleEngineProcessingDecision(true, null);
} else { } else {
log.debug("[{}] The result of msg pack processing is unsuccessful, checking unprocessed msgs and going to reprocess them", queueName);
ConcurrentMap<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> toReprocess = new ConcurrentHashMap<>(initialTotalCount); ConcurrentMap<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> toReprocess = new ConcurrentHashMap<>(initialTotalCount);
if (retryFailed) { if (retryFailed) {
result.getFailedMap().forEach(toReprocess::put); result.getFailedMap().forEach(toReprocess::put);
} else if (log.isDebugEnabled() && !result.getFailedMap().isEmpty()) {
log.debug("[{}] Skipped {} failed messages due to the processing strategy configuration", queueName, result.getFailedMap().size());
} }
if (retryTimeout) { if (retryTimeout) {
result.getPendingMap().forEach(toReprocess::put); result.getPendingMap().forEach(toReprocess::put);
} else if (log.isDebugEnabled() && !result.getPendingMap().isEmpty()) {
log.debug("[{}] Skipped {} timedOut messages due to the processing strategy configuration", queueName, result.getPendingMap().size());
} }
if (retrySuccessful) { if (retrySuccessful) {
result.getSuccessMap().forEach(toReprocess::put); result.getSuccessMap().forEach(toReprocess::put);
} else if (log.isTraceEnabled() && !result.getSuccessMap().isEmpty()) {
log.trace("[{}] Skipped {} successful messages due to the processing strategy configuration", queueName, result.getSuccessMap().size());
}
if (CollectionUtils.isEmpty(toReprocess)) {
if (log.isDebugEnabled()) {
log.debug("[{}] Stopping the reprocessing logic due to reprocessing map is empty", queueName);
}
return new TbRuleEngineProcessingDecision(true, null);
} }
log.debug("[{}] Going to reprocess {} messages", queueName, toReprocess.size()); log.debug("[{}] Going to reprocess {} messages", queueName, toReprocess.size());
if (log.isTraceEnabled()) { if (log.isTraceEnabled()) {

5
application/src/main/java/org/thingsboard/server/service/security/auth/jwt/extractor/JwtHeaderTokenExtractor.java

@ -30,7 +30,10 @@ public class JwtHeaderTokenExtractor implements TokenExtractor {
public String extract(HttpServletRequest request) { public String extract(HttpServletRequest request) {
String header = request.getHeader(ThingsboardSecurityConfiguration.JWT_TOKEN_HEADER_PARAM); String header = request.getHeader(ThingsboardSecurityConfiguration.JWT_TOKEN_HEADER_PARAM);
if (StringUtils.isBlank(header)) { if (StringUtils.isBlank(header)) {
throw new AuthenticationServiceException("Authorization header cannot be blank!"); header = request.getHeader(ThingsboardSecurityConfiguration.JWT_TOKEN_HEADER_PARAM_V2);
if (StringUtils.isBlank(header)) {
throw new AuthenticationServiceException("Authorization header cannot be blank!");
}
} }
if (header.length() < HEADER_PREFIX.length()) { if (header.length() < HEADER_PREFIX.length()) {

7
application/src/main/java/org/thingsboard/server/service/security/auth/rest/LoginRequest.java

@ -17,9 +17,14 @@ package org.thingsboard.server.service.security.auth.rest;
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@ApiModel
public class LoginRequest { public class LoginRequest {
private String username; private String username;
private String password; private String password;
@JsonCreator @JsonCreator
@ -28,10 +33,12 @@ public class LoginRequest {
this.password = password; this.password = password;
} }
@ApiModelProperty(position = 1, required = true, value = "User email", example = "tenant@thingsboard.org")
public String getUsername() { public String getUsername() {
return username; return username;
} }
@ApiModelProperty(position = 2, required = true, value = "User password", example = "tenant")
public String getPassword() { public String getPassword() {
return password; return password;
} }

34
application/src/main/java/org/thingsboard/server/service/security/auth/rest/LoginResponse.java

@ -0,0 +1,34 @@
/**
* Copyright © 2016-2021 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.security.auth.rest;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ApiModel
@Data
public class LoginResponse {
@ApiModelProperty(position = 1, required = true, value = "JWT token",
example = "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0ZW5hbnRAdGhpbmdzYm9hcmQub3JnIi...")
private String token;
@ApiModelProperty(position = 2, required = true, value = "Refresh token",
example = "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0ZW5hbnRAdGhpbmdzYm9hcmQub3JnIi...")
private String refreshToken;
}

2
application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java

@ -58,7 +58,7 @@ public enum Resource {
public static Resource of(EntityType entityType) { public static Resource of(EntityType entityType) {
for (Resource resource : Resource.values()) { for (Resource resource : Resource.values()) {
if (resource.getEntityType().get() == entityType) { if (resource.getEntityType().orElse(null) == entityType) {
return resource; return resource;
} }
} }

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

@ -23,14 +23,34 @@ server:
ssl: ssl:
# Enable/disable SSL support # Enable/disable SSL support
enabled: "${SSL_ENABLED:false}" enabled: "${SSL_ENABLED:false}"
# Path to the key store that holds the SSL certificate # Server SSL credentials
key-store: "${SSL_KEY_STORE:classpath:keystore/keystore.p12}" credentials:
# Password used to access the key store # Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore)
key-store-password: "${SSL_KEY_STORE_PASSWORD:thingsboard}" type: "${SSL_CREDENTIALS_TYPE:PEM}"
# Type of the key store # PEM server credentials
key-store-type: "${SSL_KEY_STORE_TYPE:PKCS12}" pem:
# Alias that identifies the key in the key store # Path to the server certificate file (holds server certificate or certificate chain, may include server private key)
key-alias: "${SSL_KEY_ALIAS:tomcat}" cert_file: "${SSL_PEM_CERT:server.pem}"
# Path to the server certificate private key file (optional)
key_file: "${SSL_PEM_KEY:server_key.pem}"
# Server certificate private key password (optional)
key_password: "${SSL_PEM_KEY_PASSWORD:server_key_password}"
# Keystore server credentials
keystore:
# Type of the key store
type: "${SSL_KEY_STORE_TYPE:PKCS12}"
# Path to the key store that holds the SSL certificate
store_file: "${SSL_KEY_STORE:classpath:keystore/keystore.p12}"
# Password used to access the key store
store_password: "${SSL_KEY_STORE_PASSWORD:thingsboard}"
# Key alias
key_alias: "${SSL_KEY_ALIAS:tomcat}"
# Password used to access the key
key_password: "${SSL_KEY_PASSWORD:thingsboard}"
# HTTP/2 support (takes effect only if server SSL is enabled)
http2:
# Enable/disable HTTP/2 support
enabled: "${HTTP2_ENABLED:true}"
log_controller_error_stack_trace: "${HTTP_LOG_CONTROLLER_ERROR_STACK_TRACE:false}" log_controller_error_stack_trace: "${HTTP_LOG_CONTROLLER_ERROR_STACK_TRACE:false}"
ws: ws:
send_timeout: "${TB_SERVER_WS_SEND_TIMEOUT:5000}" send_timeout: "${TB_SERVER_WS_SEND_TIMEOUT:5000}"
@ -69,6 +89,11 @@ server:
# Default value of the server side RPC timeout. # Default value of the server side RPC timeout.
default_timeout: "${DEFAULT_SERVER_SIDE_RPC_TIMEOUT:10000}" default_timeout: "${DEFAULT_SERVER_SIDE_RPC_TIMEOUT:10000}"
# Application info
app:
# Application version
version: "@project.version@"
# Zookeeper connection parameters. Used for service discovery. # Zookeeper connection parameters. Used for service discovery.
zk: zk:
# Enable/disable zookeeper discovery service. # Enable/disable zookeeper discovery service.
@ -486,6 +511,8 @@ spring:
repositories: repositories:
enabled: "true" enabled: "true"
jpa: jpa:
properties:
javax.persistence.query.timeout: "${JAVAX_PERSISTENCE_QUERY_TIMEOUT:30000}"
open-in-view: "false" open-in-view: "false"
hibernate: hibernate:
ddl-auto: "none" ddl-auto: "none"
@ -619,14 +646,28 @@ transport:
bind_port: "${MQTT_SSL_BIND_PORT:8883}" bind_port: "${MQTT_SSL_BIND_PORT:8883}"
# SSL protocol: See http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#SSLContext # SSL protocol: See http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#SSLContext
protocol: "${MQTT_SSL_PROTOCOL:TLSv1.2}" protocol: "${MQTT_SSL_PROTOCOL:TLSv1.2}"
# Path to the key store that holds the SSL certificate # Server SSL credentials
key_store: "${MQTT_SSL_KEY_STORE:mqttserver.jks}" credentials:
# Password used to access the key store # Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore)
key_store_password: "${MQTT_SSL_KEY_STORE_PASSWORD:server_ks_password}" type: "${MQTT_SSL_CREDENTIALS_TYPE:PEM}"
# Password used to access the key # PEM server credentials
key_password: "${MQTT_SSL_KEY_PASSWORD:server_key_password}" pem:
# Type of the key store # Path to the server certificate file (holds server certificate or certificate chain, may include server private key)
key_store_type: "${MQTT_SSL_KEY_STORE_TYPE:JKS}" cert_file: "${MQTT_SSL_PEM_CERT:mqttserver.pem}"
# Path to the server certificate private key file (optional)
key_file: "${MQTT_SSL_PEM_KEY:mqttserver_key.pem}"
# Server certificate private key password (optional)
key_password: "${MQTT_SSL_PEM_KEY_PASSWORD:server_key_password}"
# Keystore server credentials
keystore:
# Type of the key store
type: "${MQTT_SSL_KEY_STORE_TYPE:JKS}"
# Path to the key store that holds the SSL certificate
store_file: "${MQTT_SSL_KEY_STORE:mqttserver.jks}"
# Password used to access the key store
store_password: "${MQTT_SSL_KEY_STORE_PASSWORD:server_ks_password}"
# Password used to access the key
key_password: "${MQTT_SSL_KEY_PASSWORD:server_key_password}"
# Skip certificate validity check for client certificates. # Skip certificate validity check for client certificates.
skip_validity_check_for_client_cert: "${MQTT_SSL_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}" skip_validity_check_for_client_cert: "${MQTT_SSL_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}"
# Local CoAP transport parameters # Local CoAP transport parameters
@ -645,14 +686,30 @@ transport:
bind_address: "${COAP_DTLS_BIND_ADDRESS:0.0.0.0}" bind_address: "${COAP_DTLS_BIND_ADDRESS:0.0.0.0}"
# CoAP DTLS bind port # CoAP DTLS bind port
bind_port: "${COAP_DTLS_BIND_PORT:5684}" bind_port: "${COAP_DTLS_BIND_PORT:5684}"
# Path to the key store that holds the certificate # Server DTLS credentials
key_store: "${COAP_DTLS_KEY_STORE:coapserver.jks}" credentials:
# Password used to access the key store # Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore)
key_store_password: "${COAP_DTLS_KEY_STORE_PASSWORD:server_ks_password}" type: "${COAP_DTLS_CREDENTIALS_TYPE:PEM}"
# Password used to access the key # PEM server credentials
key_password: "${COAP_DTLS_KEY_PASSWORD:server_key_password}" pem:
# Key alias # Path to the server certificate file (holds server certificate or certificate chain, may include server private key)
key_alias: "${COAP_DTLS_KEY_ALIAS:serveralias}" cert_file: "${COAP_DTLS_PEM_CERT:coapserver.pem}"
# Path to the server certificate private key file (optional)
key_file: "${COAP_DTLS_PEM_KEY:coapserver_key.pem}"
# Server certificate private key password (optional)
key_password: "${COAP_DTLS_PEM_KEY_PASSWORD:server_key_password}"
# Keystore server credentials
keystore:
# Type of the key store
type: "${COAP_DTLS_KEY_STORE_TYPE:JKS}"
# Path to the key store that holds the SSL certificate
store_file: "${COAP_DTLS_KEY_STORE:coapserver.jks}"
# Password used to access the key store
store_password: "${COAP_DTLS_KEY_STORE_PASSWORD:server_ks_password}"
# Key alias
key_alias: "${COAP_DTLS_KEY_ALIAS:serveralias}"
# Password used to access the key
key_password: "${COAP_DTLS_KEY_PASSWORD:server_key_password}"
x509: x509:
# Skip certificate validity check for client certificates. # Skip certificate validity check for client certificates.
skip_validity_check_for_client_cert: "${TB_COAP_X509_DTLS_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}" skip_validity_check_for_client_cert: "${TB_COAP_X509_DTLS_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}"
@ -669,9 +726,33 @@ transport:
security: security:
bind_address: "${LWM2M_SECURITY_BIND_ADDRESS:0.0.0.0}" bind_address: "${LWM2M_SECURITY_BIND_ADDRESS:0.0.0.0}"
bind_port: "${LWM2M_SECURITY_BIND_PORT:5686}" bind_port: "${LWM2M_SECURITY_BIND_PORT:5686}"
# Server X509 Certificates support
credentials:
# Whether to enable LWM2M server X509 Certificate/RPK support
enabled: "${LWM2M_SERVER_CREDENTIALS_ENABLED:true}"
# Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore)
type: "${LWM2M_SERVER_CREDENTIALS_TYPE:PEM}"
# PEM server credentials
pem:
# Path to the server certificate file (holds server certificate or certificate chain, may include server private key)
cert_file: "${LWM2M_SERVER_PEM_CERT:lwm2mserver.pem}"
# Path to the server certificate private key file (optional)
key_file: "${LWM2M_SERVER_PEM_KEY:lwm2mserver_key.pem}"
# Server certificate private key password (optional)
key_password: "${LWM2M_SERVER_PEM_KEY_PASSWORD:server_key_password}"
# Keystore server credentials
keystore:
# Type of the key store
type: "${LWM2M_SERVER_KEY_STORE_TYPE:JKS}"
# Path to the key store that holds the SSL certificate
store_file: "${LWM2M_SERVER_KEY_STORE:lwm2mserver.jks}"
# Password used to access the key store
store_password: "${LWM2M_SERVER_KEY_STORE_PASSWORD:server_ks_password}"
# Key alias
key_alias: "${LWM2M_SERVER_KEY_ALIAS:server}"
# Password used to access the key
key_password: "${LWM2M_SERVER_KEY_PASSWORD:server_ks_password}"
# Only Certificate_x509: # Only Certificate_x509:
key_alias: "${LWM2M_SERVER_KEY_ALIAS:server}"
key_password: "${LWM2M_SERVER_KEY_PASSWORD:server_ks_password}"
skip_validity_check_for_client_cert: "${TB_LWM2M_SERVER_SECURITY_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}" skip_validity_check_for_client_cert: "${TB_LWM2M_SERVER_SECURITY_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}"
bootstrap: bootstrap:
enable: "${LWM2M_ENABLED_BS:true}" enable: "${LWM2M_ENABLED_BS:true}"
@ -681,18 +762,51 @@ transport:
security: security:
bind_address: "${LWM2M_BS_SECURITY_BIND_ADDRESS:0.0.0.0}" bind_address: "${LWM2M_BS_SECURITY_BIND_ADDRESS:0.0.0.0}"
bind_port: "${LWM2M_BS_SECURITY_BIND_PORT:5688}" bind_port: "${LWM2M_BS_SECURITY_BIND_PORT:5688}"
# Only Certificate_x509: # Bootstrap server X509 Certificates support
key_alias: "${LWM2M_BS_KEY_ALIAS:bootstrap}" credentials:
key_password: "${LWM2M_BS_KEY_PASSWORD:server_ks_password}" # Whether to enable LWM2M bootstrap server X509 Certificate/RPK support
enabled: "${LWM2M_BS_CREDENTIALS_ENABLED:true}"
# Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore)
type: "${LWM2M_BS_CREDENTIALS_TYPE:PEM}"
# PEM server credentials
pem:
# Path to the server certificate file (holds server certificate or certificate chain, may include server private key)
cert_file: "${LWM2M_BS_PEM_CERT:lwm2mserver.pem}"
# Path to the server certificate private key file (optional)
key_file: "${LWM2M_BS_PEM_KEY:lwm2mserver_key.pem}"
# Server certificate private key password (optional)
key_password: "${LWM2M_BS_PEM_KEY_PASSWORD:server_key_password}"
# Keystore server credentials
keystore:
# Type of the key store
type: "${LWM2M_BS_KEY_STORE_TYPE:JKS}"
# Path to the key store that holds the SSL certificate
store_file: "${LWM2M_BS_KEY_STORE:lwm2mserver.jks}"
# Password used to access the key store
store_password: "${LWM2M_BS_KEY_STORE_PASSWORD:server_ks_password}"
# Key alias
key_alias: "${LWM2M_BS_KEY_ALIAS:bootstrap}"
# Password used to access the key
key_password: "${LWM2M_BS_KEY_PASSWORD:server_ks_password}"
security: security:
# Certificate_x509: # X509 trust certificates
# To get helps about files format and how to generate it, see: https://github.com/eclipse/leshan/wiki/Credential-files-format trust-credentials:
# Create new X509 Certificates: common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_credentials.sh # Whether to load X509 trust certificates
key_store_type: "${LWM2M_KEYSTORE_TYPE:JKS}" enabled: "${LWM2M_TRUST_CREDENTIALS_ENABLED:true}"
# key_store_path_file: "${KEY_STORE_PATH_FILE:/common/transport/lwm2m/src/main/resources/credentials/serverKeyStore.jks" # Trust certificates store type (PEM - pem certificates file; KEYSTORE - java keystore)
key_store: "${LWM2M_KEYSTORE:lwm2mserver.jks}" type: "${LWM2M_TRUST_CREDENTIALS_TYPE:PEM}"
key_store_password: "${LWM2M_KEYSTORE_PASSWORD:server_ks_password}" # PEM certificates
root_alias: "${LWM2M_SERVER_ROOT_CA_ALIAS:rootca}" pem:
# Path to the certificates file (holds trust certificates)
cert_file: "${LWM2M_TRUST_PEM_CERT:lwm2mserver.pem}"
# Keystore with trust certificates
keystore:
# Type of the key store
type: "${LWM2M_TRUST_KEY_STORE_TYPE:JKS}"
# Path to the key store that holds the X509 certificates
store_file: "${LWM2M_TRUST_KEY_STORE:lwm2mserver.jks}"
# Password used to access the key store
store_password: "${LWM2M_TRUST_KEY_STORE_PASSWORD:server_ks_password}"
recommended_ciphers: "${LWM2M_RECOMMENDED_CIPHERS:false}" recommended_ciphers: "${LWM2M_RECOMMENDED_CIPHERS:false}"
recommended_supported_groups: "${LWM2M_RECOMMENDED_SUPPORTED_GROUPS:true}" recommended_supported_groups: "${LWM2M_RECOMMENDED_SUPPORTED_GROUPS:true}"
timeout: "${LWM2M_TIMEOUT:120000}" timeout: "${LWM2M_TIMEOUT:120000}"
@ -740,19 +854,19 @@ edges:
persistToTelemetry: "${EDGES_PERSIST_STATE_TO_TELEMETRY:false}" persistToTelemetry: "${EDGES_PERSIST_STATE_TO_TELEMETRY:false}"
swagger: swagger:
api_path_regex: "${SWAGGER_API_PATH_REGEX:/api.*}" api_path_regex: "${SWAGGER_API_PATH_REGEX:/api/.*}"
security_path_regex: "${SWAGGER_SECURITY_PATH_REGEX:/api.*}" security_path_regex: "${SWAGGER_SECURITY_PATH_REGEX:/api/.*}"
non_security_path_regex: "${SWAGGER_NON_SECURITY_PATH_REGEX:/api/noauth.*}" non_security_path_regex: "${SWAGGER_NON_SECURITY_PATH_REGEX:/api/(?:noauth|v1)/.*}"
title: "${SWAGGER_TITLE:ThingsBoard REST API}" title: "${SWAGGER_TITLE:ThingsBoard REST API}"
description: "${SWAGGER_DESCRIPTION:For instructions how to authorize requests please visit <a href='http://thingsboard.io/docs/reference/rest-api/'>REST API documentation page</a>.}" description: "${SWAGGER_DESCRIPTION: ThingsBoard open-source IoT platform REST API documentation.}"
contact: contact:
name: "${SWAGGER_CONTACT_NAME:Thingsboard team}" name: "${SWAGGER_CONTACT_NAME:ThingsBoard team}"
url: "${SWAGGER_CONTACT_URL:http://thingsboard.io}" url: "${SWAGGER_CONTACT_URL:https://thingsboard.io}"
email: "${SWAGGER_CONTACT_EMAIL:info@thingsboard.io}" email: "${SWAGGER_CONTACT_EMAIL:info@thingsboard.io}"
license: license:
title: "${SWAGGER_LICENSE_TITLE:Apache License Version 2.0}" title: "${SWAGGER_LICENSE_TITLE:Apache License Version 2.0}"
url: "${SWAGGER_LICENSE_URL:https://github.com/thingsboard/thingsboard/blob/master/LICENSE}" url: "${SWAGGER_LICENSE_URL:https://github.com/thingsboard/thingsboard/blob/master/LICENSE}"
version: "${SWAGGER_VERSION:2.0}" version: "${SWAGGER_VERSION:}"
queue: queue:
type: "${TB_QUEUE_TYPE:in-memory}" # in-memory or kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ) type: "${TB_QUEUE_TYPE:in-memory}" # in-memory or kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ)
@ -781,10 +895,15 @@ queue:
sasl.mechanism: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_MECHANISM:PLAIN}" sasl.mechanism: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_MECHANISM:PLAIN}"
sasl.config: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_JAAS_CONFIG:org.apache.kafka.common.security.plain.PlainLoginModule required username=\"CLUSTER_API_KEY\" password=\"CLUSTER_API_SECRET\";}" sasl.config: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_JAAS_CONFIG:org.apache.kafka.common.security.plain.PlainLoginModule required username=\"CLUSTER_API_KEY\" password=\"CLUSTER_API_SECRET\";}"
security.protocol: "${TB_QUEUE_KAFKA_CONFLUENT_SECURITY_PROTOCOL:SASL_SSL}" security.protocol: "${TB_QUEUE_KAFKA_CONFLUENT_SECURITY_PROTOCOL:SASL_SSL}"
# Key-value properties for Kafka consumer per specific topic, e.g. tb_ota_package is a topic name for ota, tb_rule_engine.sq is a topic name for default SequentialByOriginator queue.
# Check TB_QUEUE_CORE_OTA_TOPIC and TB_QUEUE_RE_SQ_TOPIC params
consumer-properties-per-topic: consumer-properties-per-topic:
tb_ota_package: tb_ota_package:
- key: max.poll.records - key: max.poll.records
value: 10 value: "${TB_QUEUE_KAFKA_OTA_MAX_POLL_RECORDS:10}"
# tb_rule_engine.sq:
# - key: max.poll.records
# value: "${TB_QUEUE_KAFKA_SQ_MAX_POLL_RECORDS:1024}"
other: # In this section you can specify custom parameters for Kafka consumer/producer and expose the env variables to configure outside other: # 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 - key: "request.timeout.ms" # refer to https://docs.confluent.io/platform/current/installation/configuration/producer-configs.html#producerconfigs_request.timeout.ms
value: "${TB_QUEUE_KAFKA_REQUEST_TIMEOUT_MS:30000}" # (30 seconds) value: "${TB_QUEUE_KAFKA_REQUEST_TIMEOUT_MS:30000}" # (30 seconds)

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

@ -99,6 +99,20 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
Assert.assertEquals(foundAsset.getName(), savedAsset.getName()); Assert.assertEquals(foundAsset.getName(), savedAsset.getName());
} }
@Test
public void testSaveAssetWithViolationOfLengthValidation() throws Exception {
Asset asset = new Asset();
asset.setName(RandomStringUtils.randomAlphabetic(300));
asset.setType("default");
doPost("/api/asset", asset).andExpect(statusReason(containsString("length of name must be equal or less than 255")));
asset.setName("Normal name");
asset.setType(RandomStringUtils.randomAlphabetic(300));
doPost("/api/asset", asset).andExpect(statusReason(containsString("length of type must be equal or less than 255")));
asset.setType("default");
asset.setLabel(RandomStringUtils.randomAlphabetic(300));
doPost("/api/asset", asset).andExpect(statusReason(containsString("length of label must be equal or less than 255")));
}
@Test @Test
public void testUpdateAssetFromDifferentTenant() throws Exception { public void testUpdateAssetFromDifferentTenant() throws Exception {
Asset asset = new Asset(); Asset asset = new Asset();

22
application/src/test/java/org/thingsboard/server/controller/BaseCustomerControllerTest.java

@ -90,6 +90,28 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
@Test
public void testSaveCustomerWithViolationOfValidation() throws Exception {
Customer customer = new Customer();
customer.setTitle(RandomStringUtils.randomAlphabetic(300));
doPost("/api/customer", customer).andExpect(statusReason(containsString("length of title must be equal or less than 255")));
customer.setTitle("Normal title");
customer.setCity(RandomStringUtils.randomAlphabetic(300));
doPost("/api/customer", customer).andExpect(statusReason(containsString("length of city must be equal or less than 255")));
customer.setCity("Normal city");
customer.setCountry(RandomStringUtils.randomAlphabetic(300));
doPost("/api/customer", customer).andExpect(statusReason(containsString("length of country must be equal or less than 255")));
customer.setCountry("Ukraine");
customer.setPhone(RandomStringUtils.randomAlphabetic(300));
doPost("/api/customer", customer).andExpect(statusReason(containsString("length of phone must be equal or less than 255")));
customer.setPhone("+3892555554512");
customer.setState(RandomStringUtils.randomAlphabetic(300));
doPost("/api/customer", customer).andExpect(statusReason(containsString("length of state must be equal or less than 255")));
customer.setState("Normal state");
customer.setZip(RandomStringUtils.randomAlphabetic(300));
doPost("/api/customer", customer).andExpect(statusReason(containsString("length of zip or postal code must be equal or less than 255")));
}
@Test @Test
public void testUpdateCustomerFromDifferentTenant() throws Exception { public void testUpdateCustomerFromDifferentTenant() throws Exception {
Customer customer = new Customer(); Customer customer = new Customer();

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

@ -93,6 +93,13 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
Assert.assertEquals(foundDashboard.getTitle(), savedDashboard.getTitle()); Assert.assertEquals(foundDashboard.getTitle(), savedDashboard.getTitle());
} }
@Test
public void testSaveDashboardInfoWithViolationOfValidation() throws Exception {
Dashboard dashboard = new Dashboard();
dashboard.setTitle(RandomStringUtils.randomAlphabetic(300));
doPost("/api/dashboard", dashboard).andExpect(statusReason(containsString("length of title must be equal or less than 255")));
}
@Test @Test
public void testUpdateDashboardFromDifferentTenant() throws Exception { public void testUpdateDashboardFromDifferentTenant() throws Exception {
Dashboard dashboard = new Dashboard(); Dashboard dashboard = new Dashboard();

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

@ -114,6 +114,20 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
Assert.assertEquals(foundDevice.getName(), savedDevice.getName()); Assert.assertEquals(foundDevice.getName(), savedDevice.getName());
} }
@Test
public void saveDeviceWithViolationOfValidation() throws Exception {
Device device = new Device();
device.setName(RandomStringUtils.randomAlphabetic(300));
device.setType("default");
doPost("/api/device", device).andExpect(statusReason(containsString("length of name must be equal or less than 255")));
device.setName("Normal Name");
device.setType(RandomStringUtils.randomAlphabetic(300));
doPost("/api/device", device).andExpect(statusReason(containsString("length of type must be equal or less than 255")));
device.setType("Normal type");
device.setLabel(RandomStringUtils.randomAlphabetic(300));
doPost("/api/device", device).andExpect(statusReason(containsString("length of label must be equal or less than 255")));
}
@Test @Test
public void testUpdateDeviceFromDifferentTenant() throws Exception { public void testUpdateDeviceFromDifferentTenant() throws Exception {
Device device = new Device(); Device device = new Device();

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

@ -22,6 +22,7 @@ import com.google.protobuf.DynamicMessage;
import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.util.JsonFormat; import com.google.protobuf.util.JsonFormat;
import com.squareup.wire.schema.internal.parser.ProtoFileElement; import com.squareup.wire.schema.internal.parser.ProtoFileElement;
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
@ -109,6 +110,12 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController
Assert.assertEquals(savedDeviceProfile.getName(), foundDeviceProfile.getName()); Assert.assertEquals(savedDeviceProfile.getName(), foundDeviceProfile.getName());
} }
@Test
public void saveDeviceProfileWithViolationOfValidation() throws Exception {
doPost("/api/deviceProfile", this.createDeviceProfile(RandomStringUtils.randomAlphabetic(300), null))
.andExpect(statusReason(containsString("length of name must be equal or less than 255")));
}
@Test @Test
public void testFindDeviceProfileById() throws Exception { public void testFindDeviceProfileById() throws Exception {
DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null);

13
application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java

@ -48,6 +48,7 @@ import org.thingsboard.server.gen.edge.v1.UserUpdateMsg;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Random;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.nullValue;
@ -112,6 +113,18 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
Assert.assertEquals(foundEdge.getName(), savedEdge.getName()); Assert.assertEquals(foundEdge.getName(), savedEdge.getName());
} }
@Test
public void testSaveEdgeWithViolationOfLengthValidation() throws Exception {
Edge edge = constructEdge(RandomStringUtils.randomAlphabetic(300), "default");
doPost("/api/edge", edge).andExpect(statusReason(containsString("length of name must be equal or less than 255")));
edge.setName("normal name");
edge.setType(RandomStringUtils.randomAlphabetic(300));
doPost("/api/edge", edge).andExpect(statusReason(containsString("length of type must be equal or less than 255")));
edge.setType("normal type");
edge.setLabel(RandomStringUtils.randomAlphabetic(300));
doPost("/api/edge", edge).andExpect(statusReason(containsString("length of label must be equal or less than 255")));
}
@Test @Test
public void testFindEdgeById() throws Exception { public void testFindEdgeById() throws Exception {
Edge edge = constructEdge("My edge", "default"); Edge edge = constructEdge("My edge", "default");

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

@ -133,6 +133,14 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
assertEquals(foundEntityView.getKeys(), telemetry); assertEquals(foundEntityView.getKeys(), telemetry);
} }
@Test
public void testSaveEntityViewWithViolationOfValidation() throws Exception {
EntityView entityView = createEntityView(RandomStringUtils.randomAlphabetic(300), 0, 0);
doPost("/api/entityView", entityView).andExpect(statusReason(containsString("length of name must be equal or less than 255")));
entityView.setName("Normal name");
entityView.setType(RandomStringUtils.randomAlphabetic(300));
doPost("/api/entityView", entityView).andExpect(statusReason(containsString("length of type must be equal or less than 255")));
}
@Test @Test
public void testUpdateEntityViewFromDifferentTenant() throws Exception { public void testUpdateEntityViewFromDifferentTenant() throws Exception {

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

@ -16,6 +16,7 @@
package org.thingsboard.server.controller; package org.thingsboard.server.controller;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
@ -40,6 +41,7 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE; import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE;
@ -117,6 +119,24 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes
Assert.assertEquals(foundFirmwareInfo.getTitle(), savedFirmwareInfo.getTitle()); Assert.assertEquals(foundFirmwareInfo.getTitle(), savedFirmwareInfo.getTitle());
} }
@Test
public void saveOtaPackageInfoWithViolationOfLengthValidation() throws Exception {
SaveOtaPackageInfoRequest firmwareInfo = new SaveOtaPackageInfoRequest();
firmwareInfo.setDeviceProfileId(deviceProfileId);
firmwareInfo.setType(FIRMWARE);
firmwareInfo.setTitle(RandomStringUtils.randomAlphabetic(300));
firmwareInfo.setVersion(VERSION);
firmwareInfo.setUsesUrl(false);
doPost("/api/otaPackage", firmwareInfo).andExpect(statusReason(containsString("length of title must be equal or less than 255")));
firmwareInfo.setTitle(TITLE);
firmwareInfo.setVersion(RandomStringUtils.randomAlphabetic(300));
doPost("/api/otaPackage", firmwareInfo).andExpect(statusReason(containsString("length of version must be equal or less than 255")));
firmwareInfo.setVersion(VERSION);
firmwareInfo.setUsesUrl(true);
firmwareInfo.setUrl(RandomStringUtils.randomAlphabetic(300));
doPost("/api/otaPackage", firmwareInfo).andExpect(statusReason(containsString("length of url must be equal or less than 255")));
}
@Test @Test
public void testSaveFirmwareData() throws Exception { public void testSaveFirmwareData() throws Exception {
SaveOtaPackageInfoRequest firmwareInfo = new SaveOtaPackageInfoRequest(); SaveOtaPackageInfoRequest firmwareInfo = new SaveOtaPackageInfoRequest();

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

@ -16,6 +16,7 @@
package org.thingsboard.server.controller; package org.thingsboard.server.controller;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
@ -33,6 +34,7 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
public abstract class BaseRuleChainControllerTest extends AbstractControllerTest { public abstract class BaseRuleChainControllerTest extends AbstractControllerTest {
@ -84,6 +86,13 @@ public abstract class BaseRuleChainControllerTest extends AbstractControllerTest
Assert.assertEquals(savedRuleChain.getName(), foundRuleChain.getName()); Assert.assertEquals(savedRuleChain.getName(), foundRuleChain.getName());
} }
@Test
public void testSaveRuleChainWithViolationOfLengthValidation() throws Exception {
RuleChain ruleChain = new RuleChain();
ruleChain.setName(RandomStringUtils.randomAlphabetic(300));
doPost("/api/ruleChain", ruleChain).andExpect(statusReason(containsString("length of name must be equal or less than 255")));
}
@Test @Test
public void testFindRuleChainById() throws Exception { public void testFindRuleChainById() throws Exception {
RuleChain ruleChain = new RuleChain(); RuleChain ruleChain = new RuleChain();

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

@ -16,6 +16,7 @@
package org.thingsboard.server.controller; package org.thingsboard.server.controller;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
@ -33,6 +34,7 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
public abstract class BaseTbResourceControllerTest extends AbstractControllerTest { public abstract class BaseTbResourceControllerTest extends AbstractControllerTest {
@ -98,6 +100,16 @@ public abstract class BaseTbResourceControllerTest extends AbstractControllerTes
Assert.assertEquals(foundResource.getTitle(), savedResource.getTitle()); Assert.assertEquals(foundResource.getTitle(), savedResource.getTitle());
} }
@Test
public void saveResourceInfoWithViolationOfLengthValidation() throws Exception {
TbResource resource = new TbResource();
resource.setResourceType(ResourceType.JKS);
resource.setTitle(RandomStringUtils.randomAlphabetic(300));
resource.setFileName(DEFAULT_FILE_NAME);
resource.setData("Test Data");
doPost("/api/resource", resource).andExpect(statusReason(containsString("length of title must be equal or less than 255")));
}
@Test @Test
public void testUpdateTbResourceFromDifferentTenant() throws Exception { public void testUpdateTbResourceFromDifferentTenant() throws Exception {
TbResource resource = new TbResource(); TbResource resource = new TbResource();

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

@ -52,6 +52,14 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest {
doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) doDelete("/api/tenant/"+savedTenant.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
@Test
public void testSaveTenantWithViolationOfValidation() throws Exception {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle(RandomStringUtils.randomAlphanumeric(300));
doPost("/api/tenant", tenant).andExpect(statusReason(containsString("length of title must be equal or less than 255")));
}
@Test @Test
public void testFindTenantById() throws Exception { public void testFindTenantById() throws Exception {

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

@ -16,6 +16,7 @@
package org.thingsboard.server.controller; package org.thingsboard.server.controller;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
@ -74,6 +75,13 @@ public abstract class BaseTenantProfileControllerTest extends AbstractController
Assert.assertEquals(foundTenantProfile.getName(), savedTenantProfile.getName()); Assert.assertEquals(foundTenantProfile.getName(), savedTenantProfile.getName());
} }
@Test
public void testSaveTenantProfileWithViolationOfLengthValidation() throws Exception {
loginSysAdmin();
TenantProfile tenantProfile = this.createTenantProfile(RandomStringUtils.randomAlphabetic(300));
doPost("/api/tenantProfile", tenantProfile).andExpect(statusReason(containsString("length of name must be equal or less than 255")));
}
@Test @Test
public void testFindTenantProfileById() throws Exception { public void testFindTenantProfileById() throws Exception {
loginSysAdmin(); loginSysAdmin();

22
application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java

@ -105,6 +105,28 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
@Test
public void testSaveUserWithViolationOfFiledValidation() throws Exception {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
String email = "tenant2@thingsboard.org";
User user = new User();
user.setAuthority(Authority.TENANT_ADMIN);
user.setTenantId(savedTenant.getId());
user.setEmail(email);
user.setFirstName(RandomStringUtils.randomAlphabetic(300));
user.setLastName("Downs");
doPost("/api/user", user).andExpect(statusReason(containsString("Validation error: length of first name must be equal or less than 255")));
user.setFirstName("Normal name");
user.setLastName(RandomStringUtils.randomAlphabetic(300));
doPost("/api/user", user).andExpect(statusReason(containsString("length of last name must be equal or less than 255")));
}
@Test @Test
public void testUpdateUserFromDifferentTenant() throws Exception { public void testUpdateUserFromDifferentTenant() throws Exception {
loginSysAdmin(); loginSysAdmin();

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

@ -16,6 +16,7 @@
package org.thingsboard.server.controller; package org.thingsboard.server.controller;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
@ -88,6 +89,13 @@ public abstract class BaseWidgetsBundleControllerTest extends AbstractController
Assert.assertEquals(foundWidgetsBundle.getTitle(), savedWidgetsBundle.getTitle()); Assert.assertEquals(foundWidgetsBundle.getTitle(), savedWidgetsBundle.getTitle());
} }
@Test
public void testSaveWidgetBundleWithViolationOfLengthValidation() throws Exception {
WidgetsBundle widgetsBundle = new WidgetsBundle();
widgetsBundle.setTitle(RandomStringUtils.randomAlphabetic(300));
doPost("/api/widgetsBundle", widgetsBundle).andExpect(statusReason(containsString("length of title must be equal or less than 255")));
}
@Test @Test
public void testUpdateWidgetsBundleFromDifferentTenant() throws Exception { public void testUpdateWidgetsBundleFromDifferentTenant() throws Exception {
WidgetsBundle widgetsBundle = new WidgetsBundle(); WidgetsBundle widgetsBundle = new WidgetsBundle();

97
application/src/test/java/org/thingsboard/server/service/install/update/DefaultDataUpdateServiceTest.java

@ -0,0 +1,97 @@
/**
* Copyright © 2016-2021 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.install.update;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.ActiveProfiles;
import java.io.IOException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.willCallRealMethod;
@ActiveProfiles("install")
@SpringBootTest(classes = DefaultDataUpdateService.class)
class DefaultDataUpdateServiceTest {
ObjectMapper mapper = new ObjectMapper();
@MockBean
DefaultDataUpdateService service;
@BeforeEach
void setUp() {
willCallRealMethod().given(service).convertDeviceProfileAlarmRulesForVersion330(any());
willCallRealMethod().given(service).convertDeviceProfileForVersion330(any());
}
JsonNode readFromResource(String resourceName) throws IOException {
return mapper.readTree(this.getClass().getClassLoader().getResourceAsStream(resourceName));
}
@Test
void convertDeviceProfileAlarmRulesForVersion330FirstRun() throws IOException {
JsonNode spec = readFromResource("update/330/device_profile_001_in.json");
JsonNode expected = readFromResource("update/330/device_profile_001_out.json");
assertThat(service.convertDeviceProfileForVersion330(spec.get("profileData"))).isTrue();
assertThat(spec.toPrettyString()).isEqualTo(expected.toPrettyString()); // use IDE feature <Click to see difference>
}
@Test
void convertDeviceProfileAlarmRulesForVersion330SecondRun() throws IOException {
JsonNode spec = readFromResource("update/330/device_profile_001_out.json");
JsonNode expected = readFromResource("update/330/device_profile_001_out.json");
assertThat(service.convertDeviceProfileForVersion330(spec.get("profileData"))).isFalse();
assertThat(spec.toPrettyString()).isEqualTo(expected.toPrettyString()); // use IDE feature <Click to see difference>
}
@Test
void convertDeviceProfileAlarmRulesForVersion330EmptyJson() throws JsonProcessingException {
JsonNode spec = mapper.readTree("{ }");
JsonNode expected = mapper.readTree("{ }");
assertThat(service.convertDeviceProfileForVersion330(spec)).isFalse();
assertThat(spec.toPrettyString()).isEqualTo(expected.toPrettyString());
}
@Test
void convertDeviceProfileAlarmRulesForVersion330AlarmNodeNull() throws JsonProcessingException {
JsonNode spec = mapper.readTree("{ \"alarms\" : null }");
JsonNode expected = mapper.readTree("{ \"alarms\" : null }");
assertThat(service.convertDeviceProfileForVersion330(spec)).isFalse();
assertThat(spec.toPrettyString()).isEqualTo(expected.toPrettyString());
}
@Test
void convertDeviceProfileAlarmRulesForVersion330NoAlarmNode() throws JsonProcessingException {
JsonNode spec = mapper.readTree("{ \"configuration\": { \"type\": \"DEFAULT\" } }");
JsonNode expected = mapper.readTree("{ \"configuration\": { \"type\": \"DEFAULT\" } }");
assertThat(service.convertDeviceProfileForVersion330(spec)).isFalse();
assertThat(spec.toPrettyString()).isEqualTo(expected.toPrettyString());
}
}

21
application/src/test/java/org/thingsboard/server/transport/mqtt/AbstractMqttIntegrationTest.java

@ -61,8 +61,12 @@ public abstract class AbstractMqttIntegrationTest extends AbstractTransportInteg
protected DeviceProfile deviceProfile; protected DeviceProfile deviceProfile;
protected void processBeforeTest (String deviceName, String gatewayName, TransportPayloadType payloadType, String telemetryTopic, String attributesTopic) throws Exception { protected void processBeforeTest(String deviceName, String gatewayName, TransportPayloadType payloadType, String telemetryTopic, String attributesTopic) throws Exception {
this.processBeforeTest(deviceName, gatewayName, payloadType, telemetryTopic, attributesTopic, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED); this.processBeforeTest(deviceName, gatewayName, payloadType, telemetryTopic, attributesTopic, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false);
}
protected void processBeforeTest(String deviceName, String gatewayName, TransportPayloadType payloadType, String telemetryTopic, String attributesTopic, boolean enableCompatibilityWithJsonPayloadFormat, boolean useJsonPayloadFormatForDefaultDownlinkTopics) throws Exception {
this.processBeforeTest(deviceName, gatewayName, payloadType, telemetryTopic, attributesTopic, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED, enableCompatibilityWithJsonPayloadFormat, useJsonPayloadFormatForDefaultDownlinkTopics);
} }
protected void processBeforeTest(String deviceName, protected void processBeforeTest(String deviceName,
@ -76,8 +80,9 @@ public abstract class AbstractMqttIntegrationTest extends AbstractTransportInteg
String rpcRequestProtoSchema, String rpcRequestProtoSchema,
String provisionKey, String provisionKey,
String provisionSecret, String provisionSecret,
DeviceProfileProvisionType provisionType DeviceProfileProvisionType provisionType,
) throws Exception { boolean enableCompatibilityWithJsonPayloadFormat,
boolean useJsonPayloadFormatForDefaultDownlinkTopics) throws Exception {
loginSysAdmin(); loginSysAdmin();
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
@ -106,7 +111,7 @@ public abstract class AbstractMqttIntegrationTest extends AbstractTransportInteg
gateway.setAdditionalInfo(additionalInfo); gateway.setAdditionalInfo(additionalInfo);
if (payloadType != null) { if (payloadType != null) {
DeviceProfile mqttDeviceProfile = createMqttDeviceProfile(payloadType, telemetryTopic, attributesTopic, telemetryProtoSchema, attributesProtoSchema, rpcResponseProtoSchema, rpcRequestProtoSchema, provisionKey, provisionSecret, provisionType); DeviceProfile mqttDeviceProfile = createMqttDeviceProfile(payloadType, telemetryTopic, attributesTopic, telemetryProtoSchema, attributesProtoSchema, rpcResponseProtoSchema, rpcRequestProtoSchema, provisionKey, provisionSecret, provisionType, enableCompatibilityWithJsonPayloadFormat, useJsonPayloadFormatForDefaultDownlinkTopics);
deviceProfile = doPost("/api/deviceProfile", mqttDeviceProfile, DeviceProfile.class); deviceProfile = doPost("/api/deviceProfile", mqttDeviceProfile, DeviceProfile.class);
device.setType(deviceProfile.getName()); device.setType(deviceProfile.getName());
device.setDeviceProfileId(deviceProfile.getId()); device.setDeviceProfileId(deviceProfile.getId());
@ -162,7 +167,9 @@ public abstract class AbstractMqttIntegrationTest extends AbstractTransportInteg
String telemetryProtoSchema, String attributesProtoSchema, String telemetryProtoSchema, String attributesProtoSchema,
String rpcResponseProtoSchema, String rpcRequestProtoSchema, String rpcResponseProtoSchema, String rpcRequestProtoSchema,
String provisionKey, String provisionSecret, String provisionKey, String provisionSecret,
DeviceProfileProvisionType provisionType) { DeviceProfileProvisionType provisionType,
boolean enableCompatibilityWithJsonPayloadFormat,
boolean useJsonPayloadFormatForDefaultDownlinkTopics) {
DeviceProfile deviceProfile = new DeviceProfile(); DeviceProfile deviceProfile = new DeviceProfile();
deviceProfile.setName(transportPayloadType.name()); deviceProfile.setName(transportPayloadType.name());
deviceProfile.setType(DeviceProfileType.DEFAULT); deviceProfile.setType(DeviceProfileType.DEFAULT);
@ -200,6 +207,8 @@ public abstract class AbstractMqttIntegrationTest extends AbstractTransportInteg
protoTransportPayloadConfiguration.setDeviceAttributesProtoSchema(attributesProtoSchema); protoTransportPayloadConfiguration.setDeviceAttributesProtoSchema(attributesProtoSchema);
protoTransportPayloadConfiguration.setDeviceRpcResponseProtoSchema(rpcResponseProtoSchema); protoTransportPayloadConfiguration.setDeviceRpcResponseProtoSchema(rpcResponseProtoSchema);
protoTransportPayloadConfiguration.setDeviceRpcRequestProtoSchema(rpcRequestProtoSchema); protoTransportPayloadConfiguration.setDeviceRpcRequestProtoSchema(rpcRequestProtoSchema);
protoTransportPayloadConfiguration.setEnableCompatibilityWithJsonPayloadFormat(enableCompatibilityWithJsonPayloadFormat);
protoTransportPayloadConfiguration.setUseJsonPayloadFormatForDefaultDownlinkTopics(enableCompatibilityWithJsonPayloadFormat && useJsonPayloadFormatForDefaultDownlinkTopics);
transportPayloadTypeConfiguration = protoTransportPayloadConfiguration; transportPayloadTypeConfiguration = protoTransportPayloadConfiguration;
} }
mqttDeviceProfileTransportConfiguration.setTransportPayloadTypeConfiguration(transportPayloadTypeConfiguration); mqttDeviceProfileTransportConfiguration.setTransportPayloadTypeConfiguration(transportPayloadTypeConfiguration);

588
application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/AbstractMqttAttributesIntegrationTest.java

@ -15,24 +15,72 @@
*/ */
package org.thingsboard.server.transport.mqtt.attributes; package org.thingsboard.server.transport.mqtt.attributes;
import com.github.os72.protobuf.dynamic.DynamicSchema;
import com.google.protobuf.Descriptors;
import com.google.protobuf.DynamicMessage;
import com.google.protobuf.InvalidProtocolBufferException;
import com.squareup.wire.schema.internal.parser.ProtoFileElement;
import io.netty.handler.codec.mqtt.MqttQoS;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttCallback; import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage; import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration;
import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration;
import org.thingsboard.server.gen.transport.TransportApiProtos;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest; import org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Slf4j @Slf4j
public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqttIntegrationTest { public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqttIntegrationTest {
public static final String ATTRIBUTES_SCHEMA_STR = "syntax =\"proto3\";\n" +
"\n" +
"package test;\n" +
"\n" +
"message PostAttributes {\n" +
" string attribute1 = 1;\n" +
" bool attribute2 = 2;\n" +
" double attribute3 = 3;\n" +
" int32 attribute4 = 4;\n" +
" JsonObject attribute5 = 5;\n" +
"\n" +
" message JsonObject {\n" +
" int32 someNumber = 6;\n" +
" repeated int32 someArray = 7;\n" +
" NestedJsonObject someNestedObject = 8;\n" +
" message NestedJsonObject {\n" +
" string key = 9;\n" +
" }\n" +
" }\n" +
"}";
protected static final String POST_ATTRIBUTES_PAYLOAD = "{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73," + protected static final String POST_ATTRIBUTES_PAYLOAD = "{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73," +
"\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}"; "\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}";
private static final String RESPONSE_ATTRIBUTES_PAYLOAD_DELETED = "{\"deleted\":[\"attribute5\"]}";
protected void processBeforeTest(String deviceName, String gatewayName, TransportPayloadType payloadType, String telemetryTopic, String attributesTopic) throws Exception { protected void processBeforeTest(String deviceName, String gatewayName, TransportPayloadType payloadType, String telemetryTopic, String attributesTopic) throws Exception {
super.processBeforeTest(deviceName, gatewayName, payloadType, telemetryTopic, attributesTopic); super.processBeforeTest(deviceName, gatewayName, payloadType, telemetryTopic, attributesTopic);
} }
@ -107,4 +155,544 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt
} }
} }
// subscribe to attributes updates from server methods
protected void processJsonTestSubscribeToAttributesUpdates(String attrSubTopic) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(accessToken);
TestMqttCallback onUpdateCallback = getTestMqttCallback();
client.setCallback(onUpdateCallback);
client.subscribe(attrSubTopic, MqttQoS.AT_MOST_ONCE.value());
Thread.sleep(1000);
doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
onUpdateCallback.getLatch().await(3, TimeUnit.SECONDS);
validateUpdateAttributesJsonResponse(onUpdateCallback);
TestMqttCallback onDeleteCallback = getTestMqttCallback();
client.setCallback(onDeleteCallback);
doDelete("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/SHARED_SCOPE?keys=attribute5", String.class);
onDeleteCallback.getLatch().await(3, TimeUnit.SECONDS);
validateDeleteAttributesJsonResponse(onDeleteCallback);
}
protected void processProtoTestSubscribeToAttributesUpdates(String attrSubTopic) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(accessToken);
TestMqttCallback onUpdateCallback = getTestMqttCallback();
client.setCallback(onUpdateCallback);
client.subscribe(attrSubTopic, MqttQoS.AT_MOST_ONCE.value());
Thread.sleep(1000);
doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
onUpdateCallback.getLatch().await(3, TimeUnit.SECONDS);
validateUpdateAttributesProtoResponse(onUpdateCallback);
TestMqttCallback onDeleteCallback = getTestMqttCallback();
client.setCallback(onDeleteCallback);
doDelete("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/SHARED_SCOPE?keys=attribute5", String.class);
onDeleteCallback.getLatch().await(3, TimeUnit.SECONDS);
validateDeleteAttributesProtoResponse(onDeleteCallback);
}
protected void validateUpdateAttributesJsonResponse(TestMqttCallback callback) throws InvalidProtocolBufferException {
assertNotNull(callback.getPayloadBytes());
String response = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8);
assertEquals(JacksonUtil.toJsonNode(POST_ATTRIBUTES_PAYLOAD), JacksonUtil.toJsonNode(response));
}
protected void validateDeleteAttributesJsonResponse(TestMqttCallback callback) throws InvalidProtocolBufferException {
assertNotNull(callback.getPayloadBytes());
String response = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8);
assertEquals(JacksonUtil.toJsonNode(RESPONSE_ATTRIBUTES_PAYLOAD_DELETED), JacksonUtil.toJsonNode(response));
}
protected void validateUpdateAttributesProtoResponse(TestMqttCallback callback) throws InvalidProtocolBufferException {
assertNotNull(callback.getPayloadBytes());
TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder();
List<TransportProtos.TsKvProto> tsKvProtoList = getTsKvProtoList();
attributeUpdateNotificationMsgBuilder.addAllSharedUpdated(tsKvProtoList);
TransportProtos.AttributeUpdateNotificationMsg expectedAttributeUpdateNotificationMsg = attributeUpdateNotificationMsgBuilder.build();
TransportProtos.AttributeUpdateNotificationMsg actualAttributeUpdateNotificationMsg = TransportProtos.AttributeUpdateNotificationMsg.parseFrom(callback.getPayloadBytes());
List<TransportProtos.KeyValueProto> actualSharedUpdatedList = actualAttributeUpdateNotificationMsg.getSharedUpdatedList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
List<TransportProtos.KeyValueProto> expectedSharedUpdatedList = expectedAttributeUpdateNotificationMsg.getSharedUpdatedList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
assertEquals(expectedSharedUpdatedList.size(), actualSharedUpdatedList.size());
assertTrue(actualSharedUpdatedList.containsAll(expectedSharedUpdatedList));
}
protected void validateDeleteAttributesProtoResponse(TestMqttCallback callback) throws InvalidProtocolBufferException {
assertNotNull(callback.getPayloadBytes());
TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder();
attributeUpdateNotificationMsgBuilder.addSharedDeleted("attribute5");
TransportProtos.AttributeUpdateNotificationMsg expectedAttributeUpdateNotificationMsg = attributeUpdateNotificationMsgBuilder.build();
TransportProtos.AttributeUpdateNotificationMsg actualAttributeUpdateNotificationMsg = TransportProtos.AttributeUpdateNotificationMsg.parseFrom(callback.getPayloadBytes());
assertEquals(expectedAttributeUpdateNotificationMsg.getSharedDeletedList().size(), actualAttributeUpdateNotificationMsg.getSharedDeletedList().size());
assertEquals("attribute5", actualAttributeUpdateNotificationMsg.getSharedDeletedList().get(0));
}
protected void processJsonGatewayTestSubscribeToAttributesUpdates() throws Exception {
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
TestMqttCallback onUpdateCallback = getTestMqttCallback();
client.setCallback(onUpdateCallback);
Device device = new Device();
device.setName("Gateway Device Subscribe to attribute updates");
device.setType("default");
byte[] connectPayloadBytes = getJsonConnectPayloadBytes();
publishMqttMsg(client, connectPayloadBytes, MqttTopics.GATEWAY_CONNECT_TOPIC);
Device savedDevice = doExecuteWithRetriesAndInterval(() -> doGet("/api/tenant/devices?deviceName=" + "Gateway Device Subscribe to attribute updates", Device.class),
20,
100);
assertNotNull(savedDevice);
client.subscribe(MqttTopics.GATEWAY_ATTRIBUTES_TOPIC, MqttQoS.AT_MOST_ONCE.value());
Thread.sleep(1000);
doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
onUpdateCallback.getLatch().await(3, TimeUnit.SECONDS);
validateJsonGatewayUpdateAttributesResponse(onUpdateCallback);
TestMqttCallback onDeleteCallback = getTestMqttCallback();
client.setCallback(onDeleteCallback);
doDelete("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/SHARED_SCOPE?keys=attribute5", String.class);
onDeleteCallback.getLatch().await(3, TimeUnit.SECONDS);
validateJsonGatewayDeleteAttributesResponse(onDeleteCallback);
}
protected void processProtoGatewayTestSubscribeToAttributesUpdates() throws Exception {
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
TestMqttCallback onUpdateCallback = getTestMqttCallback();
client.setCallback(onUpdateCallback);
Device device = new Device();
device.setName("Gateway Device Subscribe to attribute updates");
device.setType("default");
byte[] connectPayloadBytes = getProtoConnectPayloadBytes();
publishMqttMsg(client, connectPayloadBytes, MqttTopics.GATEWAY_CONNECT_TOPIC);
Device savedDevice = doExecuteWithRetriesAndInterval(() -> doGet("/api/tenant/devices?deviceName=" + "Gateway Device Subscribe to attribute updates", Device.class),
20,
100);
assertNotNull(savedDevice);
client.subscribe(MqttTopics.GATEWAY_ATTRIBUTES_TOPIC, MqttQoS.AT_MOST_ONCE.value());
Thread.sleep(1000);
doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
onUpdateCallback.getLatch().await(3, TimeUnit.SECONDS);
validateProtoGatewayUpdateAttributesResponse(onUpdateCallback);
TestMqttCallback onDeleteCallback = getTestMqttCallback();
client.setCallback(onDeleteCallback);
doDelete("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/SHARED_SCOPE?keys=attribute5", String.class);
onDeleteCallback.getLatch().await(3, TimeUnit.SECONDS);
validateProtoGatewayDeleteAttributesResponse(onDeleteCallback);
}
protected void validateJsonGatewayUpdateAttributesResponse(TestMqttCallback callback) throws InvalidProtocolBufferException {
assertNotNull(callback.getPayloadBytes());
String s = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8);
assertEquals(getJsonResponseGatewayAttributesUpdatedPayload(), s);
}
protected void validateJsonGatewayDeleteAttributesResponse(TestMqttCallback callback) throws InvalidProtocolBufferException {
assertNotNull(callback.getPayloadBytes());
String s = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8);
assertEquals(s, getJsonResponseGatewayAttributesDeletedPayload());
}
protected byte[] getJsonConnectPayloadBytes() {
String connectPayload = "{\"device\": \"Gateway Device Subscribe to attribute updates\", \"type\": \"" + TransportPayloadType.JSON.name() + "\"}";
return connectPayload.getBytes();
}
private static String getJsonResponseGatewayAttributesUpdatedPayload() {
return "{\"device\":\"" + "Gateway Device Subscribe to attribute updates" + "\"," +
"\"data\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}}";
}
private static String getJsonResponseGatewayAttributesDeletedPayload() {
return "{\"device\":\"" + "Gateway Device Subscribe to attribute updates" + "\",\"data\":{\"deleted\":[\"attribute5\"]}}";
}
protected void validateProtoGatewayUpdateAttributesResponse(TestMqttCallback callback) throws InvalidProtocolBufferException {
assertNotNull(callback.getPayloadBytes());
TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder();
List<TransportProtos.TsKvProto> tsKvProtoList = getTsKvProtoList();
attributeUpdateNotificationMsgBuilder.addAllSharedUpdated(tsKvProtoList);
TransportProtos.AttributeUpdateNotificationMsg expectedAttributeUpdateNotificationMsg = attributeUpdateNotificationMsgBuilder.build();
TransportApiProtos.GatewayAttributeUpdateNotificationMsg.Builder gatewayAttributeUpdateNotificationMsgBuilder = TransportApiProtos.GatewayAttributeUpdateNotificationMsg.newBuilder();
gatewayAttributeUpdateNotificationMsgBuilder.setDeviceName("Gateway Device Subscribe to attribute updates");
gatewayAttributeUpdateNotificationMsgBuilder.setNotificationMsg(expectedAttributeUpdateNotificationMsg);
TransportApiProtos.GatewayAttributeUpdateNotificationMsg expectedGatewayAttributeUpdateNotificationMsg = gatewayAttributeUpdateNotificationMsgBuilder.build();
TransportApiProtos.GatewayAttributeUpdateNotificationMsg actualGatewayAttributeUpdateNotificationMsg = TransportApiProtos.GatewayAttributeUpdateNotificationMsg.parseFrom(callback.getPayloadBytes());
assertEquals(expectedGatewayAttributeUpdateNotificationMsg.getDeviceName(), actualGatewayAttributeUpdateNotificationMsg.getDeviceName());
List<TransportProtos.KeyValueProto> actualSharedUpdatedList = actualGatewayAttributeUpdateNotificationMsg.getNotificationMsg().getSharedUpdatedList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
List<TransportProtos.KeyValueProto> expectedSharedUpdatedList = expectedGatewayAttributeUpdateNotificationMsg.getNotificationMsg().getSharedUpdatedList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
assertEquals(expectedSharedUpdatedList.size(), actualSharedUpdatedList.size());
assertTrue(actualSharedUpdatedList.containsAll(expectedSharedUpdatedList));
}
protected void validateProtoGatewayDeleteAttributesResponse(TestMqttCallback callback) throws InvalidProtocolBufferException {
assertNotNull(callback.getPayloadBytes());
TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder();
attributeUpdateNotificationMsgBuilder.addSharedDeleted("attribute5");
TransportProtos.AttributeUpdateNotificationMsg attributeUpdateNotificationMsg = attributeUpdateNotificationMsgBuilder.build();
TransportApiProtos.GatewayAttributeUpdateNotificationMsg.Builder gatewayAttributeUpdateNotificationMsgBuilder = TransportApiProtos.GatewayAttributeUpdateNotificationMsg.newBuilder();
gatewayAttributeUpdateNotificationMsgBuilder.setDeviceName("Gateway Device Subscribe to attribute updates");
gatewayAttributeUpdateNotificationMsgBuilder.setNotificationMsg(attributeUpdateNotificationMsg);
TransportApiProtos.GatewayAttributeUpdateNotificationMsg expectedGatewayAttributeUpdateNotificationMsg = gatewayAttributeUpdateNotificationMsgBuilder.build();
TransportApiProtos.GatewayAttributeUpdateNotificationMsg actualGatewayAttributeUpdateNotificationMsg = TransportApiProtos.GatewayAttributeUpdateNotificationMsg.parseFrom(callback.getPayloadBytes());
assertEquals(expectedGatewayAttributeUpdateNotificationMsg.getDeviceName(), actualGatewayAttributeUpdateNotificationMsg.getDeviceName());
TransportProtos.AttributeUpdateNotificationMsg expectedAttributeUpdateNotificationMsg = expectedGatewayAttributeUpdateNotificationMsg.getNotificationMsg();
TransportProtos.AttributeUpdateNotificationMsg actualAttributeUpdateNotificationMsg = actualGatewayAttributeUpdateNotificationMsg.getNotificationMsg();
assertEquals(expectedAttributeUpdateNotificationMsg.getSharedDeletedList().size(), actualAttributeUpdateNotificationMsg.getSharedDeletedList().size());
assertEquals("attribute5", actualAttributeUpdateNotificationMsg.getSharedDeletedList().get(0));
}
protected byte[] getProtoConnectPayloadBytes() {
TransportApiProtos.ConnectMsg connectProto = getConnectProto();
return connectProto.toByteArray();
}
private TransportApiProtos.ConnectMsg getConnectProto() {
TransportApiProtos.ConnectMsg.Builder builder = TransportApiProtos.ConnectMsg.newBuilder();
builder.setDeviceName("Gateway Device Subscribe to attribute updates");
builder.setDeviceType(TransportPayloadType.PROTOBUF.name());
return builder.build();
}
// request attributes from server methods
protected void processJsonTestRequestAttributesValuesFromTheServer(String attrPubTopic, String attrSubTopic, String attrReqTopicPrefix) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(accessToken);
postJsonAttributesAndSubscribeToTopic(savedDevice, client, attrPubTopic, attrSubTopic);
Thread.sleep(5000);
TestMqttCallback callback = getTestMqttCallback();
client.setCallback(callback);
validateJsonResponse(client, callback.getLatch(), callback, attrReqTopicPrefix);
}
protected void processProtoTestRequestAttributesValuesFromTheServer(String attrPubTopic, String attrSubTopic, String attrReqTopicPrefix) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(accessToken);
postProtoAttributesAndSubscribeToTopic(savedDevice, client, attrPubTopic, attrSubTopic);
Thread.sleep(5000);
TestMqttCallback callback = getTestMqttCallback();
client.setCallback(callback);
validateProtoResponse(client, callback.getLatch(), callback, attrReqTopicPrefix);
}
protected void processJsonTestGatewayRequestAttributesValuesFromTheServer() throws Exception {
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
postJsonGatewayDeviceClientAttributes(client);
Device savedDevice = doExecuteWithRetriesAndInterval(() -> doGet("/api/tenant/devices?deviceName=" + "Gateway Device Request Attributes", Device.class),
20,
100);
assertNotNull(savedDevice);
Thread.sleep(2000);
doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
Thread.sleep(5000);
client.subscribe(MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC, MqttQoS.AT_LEAST_ONCE.value()).waitForCompletion(TimeUnit.MINUTES.toMillis(1));
TestMqttCallback clientAttributesCallback = getTestMqttCallback();
client.setCallback(clientAttributesCallback);
validateJsonClientResponseGateway(client, clientAttributesCallback);
TestMqttCallback sharedAttributesCallback = getTestMqttCallback();
client.setCallback(sharedAttributesCallback);
validateJsonSharedResponseGateway(client, sharedAttributesCallback);
}
protected void processProtoTestGatewayRequestAttributesValuesFromTheServer() throws Exception {
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
postProtoGatewayDeviceClientAttributes(client);
Device savedDevice = doExecuteWithRetriesAndInterval(() -> doGet("/api/tenant/devices?deviceName=" + "Gateway Device Request Attributes", Device.class),
20,
100);
assertNotNull(savedDevice);
Thread.sleep(2000);
doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
Thread.sleep(5000);
client.subscribe(MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC, MqttQoS.AT_LEAST_ONCE.value()).waitForCompletion(TimeUnit.MINUTES.toMillis(1));
TestMqttCallback clientAttributesCallback = getTestMqttCallback();
client.setCallback(clientAttributesCallback);
validateProtoClientResponseGateway(client, clientAttributesCallback);
TestMqttCallback sharedAttributesCallback = getTestMqttCallback();
client.setCallback(sharedAttributesCallback);
validateProtoSharedResponseGateway(client, sharedAttributesCallback);
}
protected void postJsonAttributesAndSubscribeToTopic(Device savedDevice, MqttAsyncClient client, String attrPubTopic, String attrSubTopic) throws Exception {
doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
client.publish(attrPubTopic, new MqttMessage(POST_ATTRIBUTES_PAYLOAD.getBytes())).waitForCompletion(TimeUnit.MINUTES.toMillis(1));
client.subscribe(attrSubTopic, MqttQoS.AT_MOST_ONCE.value()).waitForCompletion(TimeUnit.MINUTES.toMillis(1));
}
protected void postProtoAttributesAndSubscribeToTopic(Device savedDevice, MqttAsyncClient client, String attrPubTopic, String attrSubTopic) throws Exception {
doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", AbstractMqttAttributesIntegrationTest.POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration();
assertTrue(transportConfiguration instanceof MqttDeviceProfileTransportConfiguration);
MqttDeviceProfileTransportConfiguration mqttTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration;
TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = mqttTransportConfiguration.getTransportPayloadTypeConfiguration();
assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration);
ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration;
ProtoFileElement transportProtoSchema = protoTransportPayloadConfiguration.getTransportProtoSchema(ATTRIBUTES_SCHEMA_STR);
DynamicSchema attributesSchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchema, ProtoTransportPayloadConfiguration.ATTRIBUTES_PROTO_SCHEMA);
DynamicMessage.Builder nestedJsonObjectBuilder = attributesSchema.newMessageBuilder("PostAttributes.JsonObject.NestedJsonObject");
Descriptors.Descriptor nestedJsonObjectBuilderDescriptor = nestedJsonObjectBuilder.getDescriptorForType();
assertNotNull(nestedJsonObjectBuilderDescriptor);
DynamicMessage nestedJsonObject = nestedJsonObjectBuilder.setField(nestedJsonObjectBuilderDescriptor.findFieldByName("key"), "value").build();
DynamicMessage.Builder jsonObjectBuilder = attributesSchema.newMessageBuilder("PostAttributes.JsonObject");
Descriptors.Descriptor jsonObjectBuilderDescriptor = jsonObjectBuilder.getDescriptorForType();
assertNotNull(jsonObjectBuilderDescriptor);
DynamicMessage jsonObject = jsonObjectBuilder
.setField(jsonObjectBuilderDescriptor.findFieldByName("someNumber"), 42)
.addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 1)
.addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 2)
.addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 3)
.setField(jsonObjectBuilderDescriptor.findFieldByName("someNestedObject"), nestedJsonObject)
.build();
DynamicMessage.Builder postAttributesBuilder = attributesSchema.newMessageBuilder("PostAttributes");
Descriptors.Descriptor postAttributesMsgDescriptor = postAttributesBuilder.getDescriptorForType();
assertNotNull(postAttributesMsgDescriptor);
DynamicMessage postAttributesMsg = postAttributesBuilder
.setField(postAttributesMsgDescriptor.findFieldByName("attribute1"), "value1")
.setField(postAttributesMsgDescriptor.findFieldByName("attribute2"), true)
.setField(postAttributesMsgDescriptor.findFieldByName("attribute3"), 42.0)
.setField(postAttributesMsgDescriptor.findFieldByName("attribute4"), 73)
.setField(postAttributesMsgDescriptor.findFieldByName("attribute5"), jsonObject)
.build();
byte[] payload = postAttributesMsg.toByteArray();
client.publish(attrPubTopic, new MqttMessage(payload));
client.subscribe(attrSubTopic, MqttQoS.AT_MOST_ONCE.value());
}
protected void postJsonGatewayDeviceClientAttributes(MqttAsyncClient client) throws Exception {
String postClientAttributes = "{\"" + "Gateway Device Request Attributes" + "\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}}";
client.publish(MqttTopics.GATEWAY_ATTRIBUTES_TOPIC, new MqttMessage(postClientAttributes.getBytes())).waitForCompletion(TimeUnit.MINUTES.toMillis(1));
}
protected void postProtoGatewayDeviceClientAttributes(MqttAsyncClient client) throws Exception {
String keys = "attribute1,attribute2,attribute3,attribute4,attribute5";
List<String> expectedKeys = Arrays.asList(keys.split(","));
TransportProtos.PostAttributeMsg postAttributeMsg = getPostAttributeMsg(expectedKeys);
TransportApiProtos.AttributesMsg.Builder attributesMsgBuilder = TransportApiProtos.AttributesMsg.newBuilder();
attributesMsgBuilder.setDeviceName("Gateway Device Request Attributes");
attributesMsgBuilder.setMsg(postAttributeMsg);
TransportApiProtos.AttributesMsg attributesMsg = attributesMsgBuilder.build();
TransportApiProtos.GatewayAttributesMsg.Builder gatewayAttributeMsgBuilder = TransportApiProtos.GatewayAttributesMsg.newBuilder();
gatewayAttributeMsgBuilder.addMsg(attributesMsg);
byte[] bytes = gatewayAttributeMsgBuilder.build().toByteArray();
client.publish(MqttTopics.GATEWAY_ATTRIBUTES_TOPIC, new MqttMessage(bytes));
}
protected void validateJsonResponse(MqttAsyncClient client, CountDownLatch latch, TestMqttCallback callback, String attrReqTopicPrefix) throws MqttException, InterruptedException, InvalidProtocolBufferException {
String keys = "attribute1,attribute2,attribute3,attribute4,attribute5";
String payloadStr = "{\"clientKeys\":\"" + keys + "\", \"sharedKeys\":\"" + keys + "\"}";
MqttMessage mqttMessage = new MqttMessage();
mqttMessage.setPayload(payloadStr.getBytes());
client.publish(attrReqTopicPrefix + "1", mqttMessage).waitForCompletion(TimeUnit.MINUTES.toMillis(1));
latch.await(1, TimeUnit.MINUTES);
assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS());
String expectedRequestPayload = "{\"client\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}},\"shared\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}}";
assertEquals(JacksonUtil.toJsonNode(expectedRequestPayload), JacksonUtil.toJsonNode(new String(callback.getPayloadBytes(), StandardCharsets.UTF_8)));
}
protected void validateProtoResponse(MqttAsyncClient client, CountDownLatch latch, TestMqttCallback callback, String attrReqTopic) throws MqttException, InterruptedException, InvalidProtocolBufferException {
String keys = "attribute1,attribute2,attribute3,attribute4,attribute5";
TransportApiProtos.AttributesRequest.Builder attributesRequestBuilder = TransportApiProtos.AttributesRequest.newBuilder();
attributesRequestBuilder.setClientKeys(keys);
attributesRequestBuilder.setSharedKeys(keys);
TransportApiProtos.AttributesRequest attributesRequest = attributesRequestBuilder.build();
MqttMessage mqttMessage = new MqttMessage();
mqttMessage.setPayload(attributesRequest.toByteArray());
client.publish(attrReqTopic + "1", mqttMessage);
latch.await(3, TimeUnit.SECONDS);
assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS());
TransportProtos.GetAttributeResponseMsg expectedAttributesResponse = getExpectedAttributeResponseMsg();
TransportProtos.GetAttributeResponseMsg actualAttributesResponse = TransportProtos.GetAttributeResponseMsg.parseFrom(callback.getPayloadBytes());
assertEquals(expectedAttributesResponse.getRequestId(), actualAttributesResponse.getRequestId());
List<TransportProtos.KeyValueProto> expectedClientKeyValueProtos = expectedAttributesResponse.getClientAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
List<TransportProtos.KeyValueProto> expectedSharedKeyValueProtos = expectedAttributesResponse.getSharedAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
List<TransportProtos.KeyValueProto> actualClientKeyValueProtos = actualAttributesResponse.getClientAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
List<TransportProtos.KeyValueProto> actualSharedKeyValueProtos = actualAttributesResponse.getSharedAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
assertTrue(actualClientKeyValueProtos.containsAll(expectedClientKeyValueProtos));
assertTrue(actualSharedKeyValueProtos.containsAll(expectedSharedKeyValueProtos));
}
private TransportProtos.GetAttributeResponseMsg getExpectedAttributeResponseMsg() {
TransportProtos.GetAttributeResponseMsg.Builder result = TransportProtos.GetAttributeResponseMsg.newBuilder();
List<TransportProtos.TsKvProto> tsKvProtoList = getTsKvProtoList();
result.addAllClientAttributeList(tsKvProtoList);
result.addAllSharedAttributeList(tsKvProtoList);
result.setRequestId(1);
return result.build();
}
protected void validateJsonClientResponseGateway(MqttAsyncClient client, TestMqttCallback callback) throws MqttException, InterruptedException, InvalidProtocolBufferException {
String payloadStr = "{\"id\": 1, \"device\": \"" + "Gateway Device Request Attributes" + "\", \"client\": true, \"keys\": [\"attribute1\", \"attribute2\", \"attribute3\", \"attribute4\", \"attribute5\"]}";
MqttMessage mqttMessage = new MqttMessage();
mqttMessage.setPayload(payloadStr.getBytes());
client.publish(MqttTopics.GATEWAY_ATTRIBUTES_REQUEST_TOPIC, mqttMessage).waitForCompletion(TimeUnit.MINUTES.toMillis(1));
callback.getLatch().await(1, TimeUnit.MINUTES);
assertEquals(MqttQoS.AT_LEAST_ONCE.value(), callback.getQoS());
String expectedRequestPayload = "{\"id\":1,\"device\":\"" + "Gateway Device Request Attributes" + "\",\"values\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}}";
assertEquals(JacksonUtil.toJsonNode(expectedRequestPayload), JacksonUtil.toJsonNode(new String(callback.getPayloadBytes(), StandardCharsets.UTF_8)));
}
protected void validateJsonSharedResponseGateway(MqttAsyncClient client, TestMqttCallback callback) throws MqttException, InterruptedException, InvalidProtocolBufferException {
String payloadStr = "{\"id\": 1, \"device\": \"" + "Gateway Device Request Attributes" + "\", \"client\": false, \"keys\": [\"attribute1\", \"attribute2\", \"attribute3\", \"attribute4\", \"attribute5\"]}";
MqttMessage mqttMessage = new MqttMessage();
mqttMessage.setPayload(payloadStr.getBytes());
client.publish(MqttTopics.GATEWAY_ATTRIBUTES_REQUEST_TOPIC, mqttMessage).waitForCompletion(TimeUnit.MINUTES.toMillis(1));
callback.getLatch().await(1, TimeUnit.MINUTES);
assertEquals(MqttQoS.AT_LEAST_ONCE.value(), callback.getQoS());
String expectedRequestPayload = "{\"id\":1,\"device\":\"" + "Gateway Device Request Attributes" + "\",\"values\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}}";
assertEquals(JacksonUtil.toJsonNode(expectedRequestPayload), JacksonUtil.toJsonNode(new String(callback.getPayloadBytes(), StandardCharsets.UTF_8)));
}
protected void validateProtoClientResponseGateway(MqttAsyncClient client, AbstractMqttAttributesIntegrationTest.TestMqttCallback callback) throws MqttException, InterruptedException, InvalidProtocolBufferException {
String keys = "attribute1,attribute2,attribute3,attribute4,attribute5";
TransportApiProtos.GatewayAttributesRequestMsg gatewayAttributesRequestMsg = getGatewayAttributesRequestMsg(keys, true);
client.publish(MqttTopics.GATEWAY_ATTRIBUTES_REQUEST_TOPIC, new MqttMessage(gatewayAttributesRequestMsg.toByteArray()));
callback.getLatch().await(3, TimeUnit.SECONDS);
assertEquals(MqttQoS.AT_LEAST_ONCE.value(), callback.getQoS());
TransportApiProtos.GatewayAttributeResponseMsg expectedGatewayAttributeResponseMsg = getExpectedGatewayAttributeResponseMsg(true);
TransportApiProtos.GatewayAttributeResponseMsg actualGatewayAttributeResponseMsg = TransportApiProtos.GatewayAttributeResponseMsg.parseFrom(callback.getPayloadBytes());
assertEquals(expectedGatewayAttributeResponseMsg.getDeviceName(), actualGatewayAttributeResponseMsg.getDeviceName());
TransportProtos.GetAttributeResponseMsg expectedResponseMsg = expectedGatewayAttributeResponseMsg.getResponseMsg();
TransportProtos.GetAttributeResponseMsg actualResponseMsg = actualGatewayAttributeResponseMsg.getResponseMsg();
assertEquals(expectedResponseMsg.getRequestId(), actualResponseMsg.getRequestId());
List<TransportProtos.KeyValueProto> expectedClientKeyValueProtos = expectedResponseMsg.getClientAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
List<TransportProtos.KeyValueProto> actualClientKeyValueProtos = actualResponseMsg.getClientAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
assertTrue(actualClientKeyValueProtos.containsAll(expectedClientKeyValueProtos));
}
protected void validateProtoSharedResponseGateway(MqttAsyncClient client, AbstractMqttAttributesIntegrationTest.TestMqttCallback callback) throws MqttException, InterruptedException, InvalidProtocolBufferException {
String keys = "attribute1,attribute2,attribute3,attribute4,attribute5";
TransportApiProtos.GatewayAttributesRequestMsg gatewayAttributesRequestMsg = getGatewayAttributesRequestMsg(keys, false);
client.publish(MqttTopics.GATEWAY_ATTRIBUTES_REQUEST_TOPIC, new MqttMessage(gatewayAttributesRequestMsg.toByteArray()));
callback.getLatch().await(3, TimeUnit.SECONDS);
assertEquals(MqttQoS.AT_LEAST_ONCE.value(), callback.getQoS());
TransportApiProtos.GatewayAttributeResponseMsg expectedGatewayAttributeResponseMsg = getExpectedGatewayAttributeResponseMsg(false);
TransportApiProtos.GatewayAttributeResponseMsg actualGatewayAttributeResponseMsg = TransportApiProtos.GatewayAttributeResponseMsg.parseFrom(callback.getPayloadBytes());
assertEquals(expectedGatewayAttributeResponseMsg.getDeviceName(), actualGatewayAttributeResponseMsg.getDeviceName());
TransportProtos.GetAttributeResponseMsg expectedResponseMsg = expectedGatewayAttributeResponseMsg.getResponseMsg();
TransportProtos.GetAttributeResponseMsg actualResponseMsg = actualGatewayAttributeResponseMsg.getResponseMsg();
assertEquals(expectedResponseMsg.getRequestId(), actualResponseMsg.getRequestId());
List<TransportProtos.KeyValueProto> expectedSharedKeyValueProtos = expectedResponseMsg.getSharedAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
List<TransportProtos.KeyValueProto> actualSharedKeyValueProtos = actualResponseMsg.getSharedAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
assertTrue(actualSharedKeyValueProtos.containsAll(expectedSharedKeyValueProtos));
}
private TransportApiProtos.GatewayAttributeResponseMsg getExpectedGatewayAttributeResponseMsg(boolean client) {
TransportApiProtos.GatewayAttributeResponseMsg.Builder gatewayAttributeResponseMsg = TransportApiProtos.GatewayAttributeResponseMsg.newBuilder();
TransportProtos.GetAttributeResponseMsg.Builder getAttributeResponseMsgBuilder = TransportProtos.GetAttributeResponseMsg.newBuilder();
List<TransportProtos.TsKvProto> tsKvProtoList = getTsKvProtoList();
if (client) {
getAttributeResponseMsgBuilder.addAllClientAttributeList(tsKvProtoList);
} else {
getAttributeResponseMsgBuilder.addAllSharedAttributeList(tsKvProtoList);
}
getAttributeResponseMsgBuilder.setRequestId(1);
TransportProtos.GetAttributeResponseMsg getAttributeResponseMsg = getAttributeResponseMsgBuilder.build();
gatewayAttributeResponseMsg.setDeviceName("Gateway Device Request Attributes");
gatewayAttributeResponseMsg.setResponseMsg(getAttributeResponseMsg);
return gatewayAttributeResponseMsg.build();
}
private TransportApiProtos.GatewayAttributesRequestMsg getGatewayAttributesRequestMsg(String keys, boolean client) {
return TransportApiProtos.GatewayAttributesRequestMsg.newBuilder()
.setClient(client)
.addAllKeys(Arrays.asList(keys.split(",")))
.setDeviceName("Gateway Device Request Attributes")
.setId(1).build();
}
} }

94
application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/AbstractMqttAttributesRequestBackwardCompatibilityIntegrationTest.java

@ -0,0 +1,94 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.mqtt.attributes.request;
import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Test;
import org.thingsboard.server.common.data.DeviceProfileProvisionType;
import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.transport.mqtt.attributes.AbstractMqttAttributesIntegrationTest;
import java.util.ArrayList;
import java.util.List;
@Slf4j
public abstract class AbstractMqttAttributesRequestBackwardCompatibilityIntegrationTest extends AbstractMqttAttributesIntegrationTest {
@After
public void afterTest() throws Exception {
processAfterTest();
}
@Test
public void testRequestAttributesValuesFromTheServerWithEnabledJsonCompatibility() throws Exception {
super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto",
TransportPayloadType.PROTOBUF, null, null, null, ATTRIBUTES_SCHEMA_STR, null, null, null, null, DeviceProfileProvisionType.DISABLED, true, false);
processProtoTestRequestAttributesValuesFromTheServer(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX);
}
@Test
public void testRequestAttributesValuesFromTheServerWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception {
super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto",
TransportPayloadType.PROTOBUF, null, null, null, ATTRIBUTES_SCHEMA_STR, null, null, null, null, DeviceProfileProvisionType.DISABLED, true, true);
processJsonTestRequestAttributesValuesFromTheServer(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX);
}
@Test
public void testRequestAttributesValuesFromTheServerOnShortTopicWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception {
super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto",
TransportPayloadType.PROTOBUF, null, null, null, ATTRIBUTES_SCHEMA_STR, null, null, null, null, DeviceProfileProvisionType.DISABLED, true, true);
processProtoTestRequestAttributesValuesFromTheServer(MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_TOPIC_PREFIX);
}
@Test
public void testRequestAttributesValuesFromTheServerOnShortProtoTopicWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception {
super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto",
TransportPayloadType.PROTOBUF, null, null, null, ATTRIBUTES_SCHEMA_STR, null, null, null, null, DeviceProfileProvisionType.DISABLED, true, true);
processProtoTestRequestAttributesValuesFromTheServer(MqttTopics.DEVICE_ATTRIBUTES_SHORT_PROTO_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_PROTO_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_PROTO_TOPIC_PREFIX);
}
@Test
public void testRequestAttributesValuesFromTheServerGatewayWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception {
super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto", TransportPayloadType.PROTOBUF, null, null, true, true);
processProtoTestGatewayRequestAttributesValuesFromTheServer();
}
@Test
public void testRequestAttributesValuesFromTheServerOnShortJsonTopicWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception {
super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto", TransportPayloadType.PROTOBUF, null, null, true, true);
processJsonTestRequestAttributesValuesFromTheServer(MqttTopics.DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_JSON_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_JSON_TOPIC_PREFIX);
}
protected List<TransportProtos.KeyValueProto> getKvProtos(List<String> expectedKeys) {
List<TransportProtos.KeyValueProto> keyValueProtos = new ArrayList<>();
TransportProtos.KeyValueProto strKeyValueProto = getKeyValueProto(expectedKeys.get(0), "value1", TransportProtos.KeyValueType.STRING_V);
TransportProtos.KeyValueProto boolKeyValueProto = getKeyValueProto(expectedKeys.get(1), "true", TransportProtos.KeyValueType.BOOLEAN_V);
TransportProtos.KeyValueProto dblKeyValueProto = getKeyValueProto(expectedKeys.get(2), "42.0", TransportProtos.KeyValueType.DOUBLE_V);
TransportProtos.KeyValueProto longKeyValueProto = getKeyValueProto(expectedKeys.get(3), "73", TransportProtos.KeyValueType.LONG_V);
TransportProtos.KeyValueProto jsonKeyValueProto = getKeyValueProto(expectedKeys.get(4), "{\"someNumber\": 42, \"someArray\": [1,2,3], \"someNestedObject\": {\"key\": \"value\"}}", TransportProtos.KeyValueType.JSON_V);
keyValueProtos.add(strKeyValueProto);
keyValueProtos.add(boolKeyValueProto);
keyValueProtos.add(dblKeyValueProto);
keyValueProtos.add(longKeyValueProto);
keyValueProtos.add(jsonKeyValueProto);
return keyValueProtos;
}
}

110
application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java

@ -15,7 +15,11 @@
*/ */
package org.thingsboard.server.transport.mqtt.attributes.request; package org.thingsboard.server.transport.mqtt.attributes.request;
import com.github.os72.protobuf.dynamic.DynamicSchema;
import com.google.protobuf.Descriptors;
import com.google.protobuf.DynamicMessage;
import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.InvalidProtocolBufferException;
import com.squareup.wire.schema.internal.parser.ProtoFileElement;
import io.netty.handler.codec.mqtt.MqttQoS; import io.netty.handler.codec.mqtt.MqttQoS;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient; import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
@ -25,16 +29,26 @@ import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration;
import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration;
import org.thingsboard.server.gen.transport.TransportApiProtos;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.transport.mqtt.attributes.AbstractMqttAttributesIntegrationTest; import org.thingsboard.server.transport.mqtt.attributes.AbstractMqttAttributesIntegrationTest;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Slf4j @Slf4j
@ -52,109 +66,21 @@ public abstract class AbstractMqttAttributesRequestIntegrationTest extends Abstr
@Test @Test
public void testRequestAttributesValuesFromTheServer() throws Exception { public void testRequestAttributesValuesFromTheServer() throws Exception {
processTestRequestAttributesValuesFromTheServer(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX); processJsonTestRequestAttributesValuesFromTheServer(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX);
} }
@Test @Test
public void testRequestAttributesValuesFromTheServerOnShortTopic() throws Exception { public void testRequestAttributesValuesFromTheServerOnShortTopic() throws Exception {
processTestRequestAttributesValuesFromTheServer(MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_TOPIC_PREFIX); processJsonTestRequestAttributesValuesFromTheServer(MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_TOPIC_PREFIX);
} }
@Test @Test
public void testRequestAttributesValuesFromTheServerOnShortJsonTopic() throws Exception { public void testRequestAttributesValuesFromTheServerOnShortJsonTopic() throws Exception {
processTestRequestAttributesValuesFromTheServer(MqttTopics.DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_JSON_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_JSON_TOPIC_PREFIX); processJsonTestRequestAttributesValuesFromTheServer(MqttTopics.DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_JSON_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_JSON_TOPIC_PREFIX);
} }
@Test @Test
public void testRequestAttributesValuesFromTheServerGateway() throws Exception { public void testRequestAttributesValuesFromTheServerGateway() throws Exception {
processTestGatewayRequestAttributesValuesFromTheServer(); processJsonTestGatewayRequestAttributesValuesFromTheServer();
}
protected void processTestRequestAttributesValuesFromTheServer(String attrPubTopic, String attrSubTopic, String attrReqTopicPrefix) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(accessToken);
postAttributesAndSubscribeToTopic(savedDevice, client, attrPubTopic, attrSubTopic);
Thread.sleep(5000);
TestMqttCallback callback = getTestMqttCallback();
client.setCallback(callback);
validateResponse(client, callback.getLatch(), callback, attrReqTopicPrefix);
}
protected void processTestGatewayRequestAttributesValuesFromTheServer() throws Exception {
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
postGatewayDeviceClientAttributes(client);
Device savedDevice = doExecuteWithRetriesAndInterval(() -> doGet("/api/tenant/devices?deviceName=" + "Gateway Device Request Attributes", Device.class),
20,
100);
assertNotNull(savedDevice);
Thread.sleep(2000);
doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
Thread.sleep(5000);
client.subscribe(MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC, MqttQoS.AT_LEAST_ONCE.value()).waitForCompletion(TimeUnit.MINUTES.toMillis(1));
TestMqttCallback clientAttributesCallback = getTestMqttCallback();
client.setCallback(clientAttributesCallback);
validateClientResponseGateway(client, clientAttributesCallback);
TestMqttCallback sharedAttributesCallback = getTestMqttCallback();
client.setCallback(sharedAttributesCallback);
validateSharedResponseGateway(client, sharedAttributesCallback);
}
protected void postAttributesAndSubscribeToTopic(Device savedDevice, MqttAsyncClient client, String attrPubTopic, String attrSubTopic) throws Exception {
doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
client.publish(attrPubTopic, new MqttMessage(POST_ATTRIBUTES_PAYLOAD.getBytes())).waitForCompletion(TimeUnit.MINUTES.toMillis(1));
client.subscribe(attrSubTopic, MqttQoS.AT_MOST_ONCE.value()).waitForCompletion(TimeUnit.MINUTES.toMillis(1));
}
protected void postGatewayDeviceClientAttributes(MqttAsyncClient client) throws Exception {
String postClientAttributes = "{\"" + "Gateway Device Request Attributes" + "\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}}";
client.publish(MqttTopics.GATEWAY_ATTRIBUTES_TOPIC, new MqttMessage(postClientAttributes.getBytes())).waitForCompletion(TimeUnit.MINUTES.toMillis(1));
}
protected void validateResponse(MqttAsyncClient client, CountDownLatch latch, TestMqttCallback callback, String attrReqTopicPrefix) throws MqttException, InterruptedException, InvalidProtocolBufferException {
String keys = "attribute1,attribute2,attribute3,attribute4,attribute5";
String payloadStr = "{\"clientKeys\":\"" + keys + "\", \"sharedKeys\":\"" + keys + "\"}";
MqttMessage mqttMessage = new MqttMessage();
mqttMessage.setPayload(payloadStr.getBytes());
client.publish(attrReqTopicPrefix + "1", mqttMessage).waitForCompletion(TimeUnit.MINUTES.toMillis(1));
latch.await(1, TimeUnit.MINUTES);
assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS());
String expectedRequestPayload = "{\"client\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}},\"shared\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}}";
assertEquals(JacksonUtil.toJsonNode(expectedRequestPayload), JacksonUtil.toJsonNode(new String(callback.getPayloadBytes(), StandardCharsets.UTF_8)));
}
protected void validateClientResponseGateway(MqttAsyncClient client, TestMqttCallback callback) throws MqttException, InterruptedException, InvalidProtocolBufferException {
String payloadStr = "{\"id\": 1, \"device\": \"" + "Gateway Device Request Attributes" + "\", \"client\": true, \"keys\": [\"attribute1\", \"attribute2\", \"attribute3\", \"attribute4\", \"attribute5\"]}";
MqttMessage mqttMessage = new MqttMessage();
mqttMessage.setPayload(payloadStr.getBytes());
client.publish(MqttTopics.GATEWAY_ATTRIBUTES_REQUEST_TOPIC, mqttMessage).waitForCompletion(TimeUnit.MINUTES.toMillis(1));
callback.getLatch().await(1, TimeUnit.MINUTES);
assertEquals(MqttQoS.AT_LEAST_ONCE.value(), callback.getQoS());
String expectedRequestPayload = "{\"id\":1,\"device\":\"" + "Gateway Device Request Attributes" + "\",\"values\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}}";
assertEquals(JacksonUtil.toJsonNode(expectedRequestPayload), JacksonUtil.toJsonNode(new String(callback.getPayloadBytes(), StandardCharsets.UTF_8)));
}
protected void validateSharedResponseGateway(MqttAsyncClient client, TestMqttCallback callback) throws MqttException, InterruptedException, InvalidProtocolBufferException {
String payloadStr = "{\"id\": 1, \"device\": \"" + "Gateway Device Request Attributes" + "\", \"client\": false, \"keys\": [\"attribute1\", \"attribute2\", \"attribute3\", \"attribute4\", \"attribute5\"]}";
MqttMessage mqttMessage = new MqttMessage();
mqttMessage.setPayload(payloadStr.getBytes());
client.publish(MqttTopics.GATEWAY_ATTRIBUTES_REQUEST_TOPIC, mqttMessage).waitForCompletion(TimeUnit.MINUTES.toMillis(1));
callback.getLatch().await(1, TimeUnit.MINUTES);
assertEquals(MqttQoS.AT_LEAST_ONCE.value(), callback.getQoS());
String expectedRequestPayload = "{\"id\":1,\"device\":\"" + "Gateway Device Request Attributes" + "\",\"values\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}}";
assertEquals(JacksonUtil.toJsonNode(expectedRequestPayload), JacksonUtil.toJsonNode(new String(callback.getPayloadBytes(), StandardCharsets.UTF_8)));
} }
} }

11
application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/AbstractMqttAttributesRequestJsonIntegrationTest.java

@ -21,9 +21,10 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.transport.mqtt.attributes.AbstractMqttAttributesIntegrationTest;
@Slf4j @Slf4j
public abstract class AbstractMqttAttributesRequestJsonIntegrationTest extends AbstractMqttAttributesRequestIntegrationTest { public abstract class AbstractMqttAttributesRequestJsonIntegrationTest extends AbstractMqttAttributesIntegrationTest {
@Before @Before
public void beforeTest() throws Exception { public void beforeTest() throws Exception {
@ -37,21 +38,21 @@ public abstract class AbstractMqttAttributesRequestJsonIntegrationTest extends A
@Test @Test
public void testRequestAttributesValuesFromTheServer() throws Exception { public void testRequestAttributesValuesFromTheServer() throws Exception {
super.testRequestAttributesValuesFromTheServer(); processJsonTestRequestAttributesValuesFromTheServer(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX);
} }
@Test @Test
public void testRequestAttributesValuesFromTheServerOnShortTopic() throws Exception { public void testRequestAttributesValuesFromTheServerOnShortTopic() throws Exception {
super.testRequestAttributesValuesFromTheServerOnShortTopic(); processJsonTestRequestAttributesValuesFromTheServer(MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_TOPIC_PREFIX);
} }
@Test @Test
public void testRequestAttributesValuesFromTheServerOnShortJsonTopic() throws Exception { public void testRequestAttributesValuesFromTheServerOnShortJsonTopic() throws Exception {
super.testRequestAttributesValuesFromTheServerOnShortJsonTopic(); processJsonTestRequestAttributesValuesFromTheServer(MqttTopics.DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_JSON_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_JSON_TOPIC_PREFIX);
} }
@Test @Test
public void testRequestAttributesValuesFromTheServerGateway() throws Exception { public void testRequestAttributesValuesFromTheServerGateway() throws Exception {
processTestGatewayRequestAttributesValuesFromTheServer(); processJsonTestGatewayRequestAttributesValuesFromTheServer();
} }
} }

213
application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java

@ -15,65 +15,20 @@
*/ */
package org.thingsboard.server.transport.mqtt.attributes.request; package org.thingsboard.server.transport.mqtt.attributes.request;
import com.github.os72.protobuf.dynamic.DynamicSchema;
import com.google.protobuf.Descriptors;
import com.google.protobuf.DynamicMessage;
import com.google.protobuf.InvalidProtocolBufferException;
import com.squareup.wire.schema.internal.parser.ProtoFileElement;
import io.netty.handler.codec.mqtt.MqttQoS;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.junit.After; import org.junit.After;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.DeviceProfileProvisionType;
import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration;
import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration;
import org.thingsboard.server.gen.transport.TransportApiProtos;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.transport.mqtt.attributes.AbstractMqttAttributesIntegrationTest; import org.thingsboard.server.transport.mqtt.attributes.AbstractMqttAttributesIntegrationTest;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Slf4j @Slf4j
public abstract class AbstractMqttAttributesRequestProtoIntegrationTest extends AbstractMqttAttributesRequestIntegrationTest { public abstract class AbstractMqttAttributesRequestProtoIntegrationTest extends AbstractMqttAttributesIntegrationTest {
public static final String ATTRIBUTES_SCHEMA_STR = "syntax =\"proto3\";\n" +
"\n" +
"package test;\n" +
"\n" +
"message PostAttributes {\n" +
" string attribute1 = 1;\n" +
" bool attribute2 = 2;\n" +
" double attribute3 = 3;\n" +
" int32 attribute4 = 4;\n" +
" JsonObject attribute5 = 5;\n" +
"\n" +
" message JsonObject {\n" +
" int32 someNumber = 6;\n" +
" repeated int32 someArray = 7;\n" +
" NestedJsonObject someNestedObject = 8;\n" +
" message NestedJsonObject {\n" +
" string key = 9;\n" +
" }\n" +
" }\n" +
"}";
@After @After
public void afterTest() throws Exception { public void afterTest() throws Exception {
@ -83,182 +38,36 @@ public abstract class AbstractMqttAttributesRequestProtoIntegrationTest extends
@Test @Test
public void testRequestAttributesValuesFromTheServer() throws Exception { public void testRequestAttributesValuesFromTheServer() throws Exception {
super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto", super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto",
TransportPayloadType.PROTOBUF, null, null, null, ATTRIBUTES_SCHEMA_STR, null, null, null, null, DeviceProfileProvisionType.DISABLED); TransportPayloadType.PROTOBUF, null, null, null, ATTRIBUTES_SCHEMA_STR, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false);
processTestRequestAttributesValuesFromTheServer(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX); processProtoTestRequestAttributesValuesFromTheServer(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX);
} }
@Test @Test
public void testRequestAttributesValuesFromTheServerOnShortTopic() throws Exception { public void testRequestAttributesValuesFromTheServerOnShortTopic() throws Exception {
super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto", super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto",
TransportPayloadType.PROTOBUF, null, null, null, ATTRIBUTES_SCHEMA_STR, null, null, null, null, DeviceProfileProvisionType.DISABLED); TransportPayloadType.PROTOBUF, null, null, null, ATTRIBUTES_SCHEMA_STR, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false);
processTestRequestAttributesValuesFromTheServer(MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_TOPIC_PREFIX); processProtoTestRequestAttributesValuesFromTheServer(MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_TOPIC_PREFIX);
} }
@Test @Test
public void testRequestAttributesValuesFromTheServerOnShortProtoTopic() throws Exception { public void testRequestAttributesValuesFromTheServerOnShortProtoTopic() throws Exception {
super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto", super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto",
TransportPayloadType.PROTOBUF, null, null, null, ATTRIBUTES_SCHEMA_STR, null, null, null, null, DeviceProfileProvisionType.DISABLED); TransportPayloadType.PROTOBUF, null, null, null, ATTRIBUTES_SCHEMA_STR, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false);
processTestRequestAttributesValuesFromTheServer(MqttTopics.DEVICE_ATTRIBUTES_SHORT_PROTO_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_PROTO_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_PROTO_TOPIC_PREFIX); processProtoTestRequestAttributesValuesFromTheServer(MqttTopics.DEVICE_ATTRIBUTES_SHORT_PROTO_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_PROTO_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_PROTO_TOPIC_PREFIX);
} }
@Test @Test
public void testRequestAttributesValuesFromTheServerGateway() throws Exception { public void testRequestAttributesValuesFromTheServerGateway() throws Exception {
super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto", TransportPayloadType.PROTOBUF, null, null); super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto", TransportPayloadType.PROTOBUF, null, null);
processTestGatewayRequestAttributesValuesFromTheServer(); processProtoTestGatewayRequestAttributesValuesFromTheServer();
} }
@Test @Test
public void testRequestAttributesValuesFromTheServerOnShortJsonTopic() throws Exception { } public void testRequestAttributesValuesFromTheServerOnShortJsonTopic() throws Exception {
super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto", TransportPayloadType.PROTOBUF, null, null);
protected void postAttributesAndSubscribeToTopic(Device savedDevice, MqttAsyncClient client, String attrPubTopic, String attrSubTopic) throws Exception { processJsonTestRequestAttributesValuesFromTheServer(MqttTopics.DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_JSON_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_JSON_TOPIC_PREFIX);
doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", AbstractMqttAttributesIntegrationTest.POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration();
assertTrue(transportConfiguration instanceof MqttDeviceProfileTransportConfiguration);
MqttDeviceProfileTransportConfiguration mqttTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration;
TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = mqttTransportConfiguration.getTransportPayloadTypeConfiguration();
assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration);
ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration;
ProtoFileElement transportProtoSchema = protoTransportPayloadConfiguration.getTransportProtoSchema(ATTRIBUTES_SCHEMA_STR);
DynamicSchema attributesSchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchema, ProtoTransportPayloadConfiguration.ATTRIBUTES_PROTO_SCHEMA);
DynamicMessage.Builder nestedJsonObjectBuilder = attributesSchema.newMessageBuilder("PostAttributes.JsonObject.NestedJsonObject");
Descriptors.Descriptor nestedJsonObjectBuilderDescriptor = nestedJsonObjectBuilder.getDescriptorForType();
assertNotNull(nestedJsonObjectBuilderDescriptor);
DynamicMessage nestedJsonObject = nestedJsonObjectBuilder.setField(nestedJsonObjectBuilderDescriptor.findFieldByName("key"), "value").build();
DynamicMessage.Builder jsonObjectBuilder = attributesSchema.newMessageBuilder("PostAttributes.JsonObject");
Descriptors.Descriptor jsonObjectBuilderDescriptor = jsonObjectBuilder.getDescriptorForType();
assertNotNull(jsonObjectBuilderDescriptor);
DynamicMessage jsonObject = jsonObjectBuilder
.setField(jsonObjectBuilderDescriptor.findFieldByName("someNumber"), 42)
.addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 1)
.addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 2)
.addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 3)
.setField(jsonObjectBuilderDescriptor.findFieldByName("someNestedObject"), nestedJsonObject)
.build();
DynamicMessage.Builder postAttributesBuilder = attributesSchema.newMessageBuilder("PostAttributes");
Descriptors.Descriptor postAttributesMsgDescriptor = postAttributesBuilder.getDescriptorForType();
assertNotNull(postAttributesMsgDescriptor);
DynamicMessage postAttributesMsg = postAttributesBuilder
.setField(postAttributesMsgDescriptor.findFieldByName("attribute1"), "value1")
.setField(postAttributesMsgDescriptor.findFieldByName("attribute2"), true)
.setField(postAttributesMsgDescriptor.findFieldByName("attribute3"), 42.0)
.setField(postAttributesMsgDescriptor.findFieldByName("attribute4"), 73)
.setField(postAttributesMsgDescriptor.findFieldByName("attribute5"), jsonObject)
.build();
byte[] payload = postAttributesMsg.toByteArray();
client.publish(attrPubTopic, new MqttMessage(payload));
client.subscribe(attrSubTopic, MqttQoS.AT_MOST_ONCE.value());
}
protected void postGatewayDeviceClientAttributes(MqttAsyncClient client) throws Exception {
String keys = "attribute1,attribute2,attribute3,attribute4,attribute5";
List<String> expectedKeys = Arrays.asList(keys.split(","));
TransportProtos.PostAttributeMsg postAttributeMsg = getPostAttributeMsg(expectedKeys);
TransportApiProtos.AttributesMsg.Builder attributesMsgBuilder = TransportApiProtos.AttributesMsg.newBuilder();
attributesMsgBuilder.setDeviceName("Gateway Device Request Attributes");
attributesMsgBuilder.setMsg(postAttributeMsg);
TransportApiProtos.AttributesMsg attributesMsg = attributesMsgBuilder.build();
TransportApiProtos.GatewayAttributesMsg.Builder gatewayAttributeMsgBuilder = TransportApiProtos.GatewayAttributesMsg.newBuilder();
gatewayAttributeMsgBuilder.addMsg(attributesMsg);
byte[] bytes = gatewayAttributeMsgBuilder.build().toByteArray();
client.publish(MqttTopics.GATEWAY_ATTRIBUTES_TOPIC, new MqttMessage(bytes));
}
protected void validateResponse(MqttAsyncClient client, CountDownLatch latch, TestMqttCallback callback, String attrReqTopic) throws MqttException, InterruptedException, InvalidProtocolBufferException {
String keys = "attribute1,attribute2,attribute3,attribute4,attribute5";
TransportApiProtos.AttributesRequest.Builder attributesRequestBuilder = TransportApiProtos.AttributesRequest.newBuilder();
attributesRequestBuilder.setClientKeys(keys);
attributesRequestBuilder.setSharedKeys(keys);
TransportApiProtos.AttributesRequest attributesRequest = attributesRequestBuilder.build();
MqttMessage mqttMessage = new MqttMessage();
mqttMessage.setPayload(attributesRequest.toByteArray());
client.publish(attrReqTopic + "1", mqttMessage);
latch.await(3, TimeUnit.SECONDS);
assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS());
TransportProtos.GetAttributeResponseMsg expectedAttributesResponse = getExpectedAttributeResponseMsg();
TransportProtos.GetAttributeResponseMsg actualAttributesResponse = TransportProtos.GetAttributeResponseMsg.parseFrom(callback.getPayloadBytes());
assertEquals(expectedAttributesResponse.getRequestId(), actualAttributesResponse.getRequestId());
List<TransportProtos.KeyValueProto> expectedClientKeyValueProtos = expectedAttributesResponse.getClientAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
List<TransportProtos.KeyValueProto> expectedSharedKeyValueProtos = expectedAttributesResponse.getSharedAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
List<TransportProtos.KeyValueProto> actualClientKeyValueProtos = actualAttributesResponse.getClientAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
List<TransportProtos.KeyValueProto> actualSharedKeyValueProtos = actualAttributesResponse.getSharedAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
assertTrue(actualClientKeyValueProtos.containsAll(expectedClientKeyValueProtos));
assertTrue(actualSharedKeyValueProtos.containsAll(expectedSharedKeyValueProtos));
}
protected void validateClientResponseGateway(MqttAsyncClient client, AbstractMqttAttributesIntegrationTest.TestMqttCallback callback) throws MqttException, InterruptedException, InvalidProtocolBufferException {
String keys = "attribute1,attribute2,attribute3,attribute4,attribute5";
TransportApiProtos.GatewayAttributesRequestMsg gatewayAttributesRequestMsg = getGatewayAttributesRequestMsg(keys, true);
client.publish(MqttTopics.GATEWAY_ATTRIBUTES_REQUEST_TOPIC, new MqttMessage(gatewayAttributesRequestMsg.toByteArray()));
callback.getLatch().await(3, TimeUnit.SECONDS);
assertEquals(MqttQoS.AT_LEAST_ONCE.value(), callback.getQoS());
TransportApiProtos.GatewayAttributeResponseMsg expectedGatewayAttributeResponseMsg = getExpectedGatewayAttributeResponseMsg(true);
TransportApiProtos.GatewayAttributeResponseMsg actualGatewayAttributeResponseMsg = TransportApiProtos.GatewayAttributeResponseMsg.parseFrom(callback.getPayloadBytes());
assertEquals(expectedGatewayAttributeResponseMsg.getDeviceName(), actualGatewayAttributeResponseMsg.getDeviceName());
TransportProtos.GetAttributeResponseMsg expectedResponseMsg = expectedGatewayAttributeResponseMsg.getResponseMsg();
TransportProtos.GetAttributeResponseMsg actualResponseMsg = actualGatewayAttributeResponseMsg.getResponseMsg();
assertEquals(expectedResponseMsg.getRequestId(), actualResponseMsg.getRequestId());
List<TransportProtos.KeyValueProto> expectedClientKeyValueProtos = expectedResponseMsg.getClientAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
List<TransportProtos.KeyValueProto> actualClientKeyValueProtos = actualResponseMsg.getClientAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
assertTrue(actualClientKeyValueProtos.containsAll(expectedClientKeyValueProtos));
}
protected void validateSharedResponseGateway(MqttAsyncClient client, AbstractMqttAttributesIntegrationTest.TestMqttCallback callback) throws MqttException, InterruptedException, InvalidProtocolBufferException {
String keys = "attribute1,attribute2,attribute3,attribute4,attribute5";
TransportApiProtos.GatewayAttributesRequestMsg gatewayAttributesRequestMsg = getGatewayAttributesRequestMsg(keys, false);
client.publish(MqttTopics.GATEWAY_ATTRIBUTES_REQUEST_TOPIC, new MqttMessage(gatewayAttributesRequestMsg.toByteArray()));
callback.getLatch().await(3, TimeUnit.SECONDS);
assertEquals(MqttQoS.AT_LEAST_ONCE.value(), callback.getQoS());
TransportApiProtos.GatewayAttributeResponseMsg expectedGatewayAttributeResponseMsg = getExpectedGatewayAttributeResponseMsg(false);
TransportApiProtos.GatewayAttributeResponseMsg actualGatewayAttributeResponseMsg = TransportApiProtos.GatewayAttributeResponseMsg.parseFrom(callback.getPayloadBytes());
assertEquals(expectedGatewayAttributeResponseMsg.getDeviceName(), actualGatewayAttributeResponseMsg.getDeviceName());
TransportProtos.GetAttributeResponseMsg expectedResponseMsg = expectedGatewayAttributeResponseMsg.getResponseMsg();
TransportProtos.GetAttributeResponseMsg actualResponseMsg = actualGatewayAttributeResponseMsg.getResponseMsg();
assertEquals(expectedResponseMsg.getRequestId(), actualResponseMsg.getRequestId());
List<TransportProtos.KeyValueProto> expectedSharedKeyValueProtos = expectedResponseMsg.getSharedAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
List<TransportProtos.KeyValueProto> actualSharedKeyValueProtos = actualResponseMsg.getSharedAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
assertTrue(actualSharedKeyValueProtos.containsAll(expectedSharedKeyValueProtos));
}
private TransportApiProtos.GatewayAttributesRequestMsg getGatewayAttributesRequestMsg(String keys, boolean client) {
return TransportApiProtos.GatewayAttributesRequestMsg.newBuilder()
.setClient(client)
.addAllKeys(Arrays.asList(keys.split(",")))
.setDeviceName("Gateway Device Request Attributes")
.setId(1).build();
}
private TransportProtos.GetAttributeResponseMsg getExpectedAttributeResponseMsg() {
TransportProtos.GetAttributeResponseMsg.Builder result = TransportProtos.GetAttributeResponseMsg.newBuilder();
List<TransportProtos.TsKvProto> tsKvProtoList = getTsKvProtoList();
result.addAllClientAttributeList(tsKvProtoList);
result.addAllSharedAttributeList(tsKvProtoList);
result.setRequestId(1);
return result.build();
} }
private TransportApiProtos.GatewayAttributeResponseMsg getExpectedGatewayAttributeResponseMsg(boolean client) {
TransportApiProtos.GatewayAttributeResponseMsg.Builder gatewayAttributeResponseMsg = TransportApiProtos.GatewayAttributeResponseMsg.newBuilder();
TransportProtos.GetAttributeResponseMsg.Builder getAttributeResponseMsgBuilder = TransportProtos.GetAttributeResponseMsg.newBuilder();
List<TransportProtos.TsKvProto> tsKvProtoList = getTsKvProtoList();
if (client) {
getAttributeResponseMsgBuilder.addAllClientAttributeList(tsKvProtoList);
} else {
getAttributeResponseMsgBuilder.addAllSharedAttributeList(tsKvProtoList);
}
getAttributeResponseMsgBuilder.setRequestId(1);
TransportProtos.GetAttributeResponseMsg getAttributeResponseMsg = getAttributeResponseMsgBuilder.build();
gatewayAttributeResponseMsg.setDeviceName("Gateway Device Request Attributes");
gatewayAttributeResponseMsg.setResponseMsg(getAttributeResponseMsg);
return gatewayAttributeResponseMsg.build();
}
protected List<TransportProtos.KeyValueProto> getKvProtos(List<String> expectedKeys) { protected List<TransportProtos.KeyValueProto> getKvProtos(List<String> expectedKeys) {
List<TransportProtos.KeyValueProto> keyValueProtos = new ArrayList<>(); List<TransportProtos.KeyValueProto> keyValueProtos = new ArrayList<>();

23
application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/sql/MqttAttributesRequestBackwardCompatibilityIntegrationTest.java

@ -0,0 +1,23 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.mqtt.attributes.request.sql;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.transport.mqtt.attributes.request.AbstractMqttAttributesRequestBackwardCompatibilityIntegrationTest;
@DaoSqlTest
public class MqttAttributesRequestBackwardCompatibilityIntegrationTest extends AbstractMqttAttributesRequestBackwardCompatibilityIntegrationTest {
}

2
application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/sql/MqttAttributesRequestSqlIntegrationTest.java → application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/sql/MqttAttributesRequestIntegrationTest.java

@ -19,5 +19,5 @@ import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.transport.mqtt.attributes.request.AbstractMqttAttributesRequestIntegrationTest; import org.thingsboard.server.transport.mqtt.attributes.request.AbstractMqttAttributesRequestIntegrationTest;
@DaoSqlTest @DaoSqlTest
public class MqttAttributesRequestSqlIntegrationTest extends AbstractMqttAttributesRequestIntegrationTest { public class MqttAttributesRequestIntegrationTest extends AbstractMqttAttributesRequestIntegrationTest {
} }

2
application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/sql/MqttAttributesRequestJsonSqlIntegrationTest.java → application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/sql/MqttAttributesRequestJsonIntegrationTest.java

@ -19,5 +19,5 @@ import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.transport.mqtt.attributes.request.AbstractMqttAttributesRequestJsonIntegrationTest; import org.thingsboard.server.transport.mqtt.attributes.request.AbstractMqttAttributesRequestJsonIntegrationTest;
@DaoSqlTest @DaoSqlTest
public class MqttAttributesRequestJsonSqlIntegrationTest extends AbstractMqttAttributesRequestJsonIntegrationTest { public class MqttAttributesRequestJsonIntegrationTest extends AbstractMqttAttributesRequestJsonIntegrationTest {
} }

2
application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/sql/MqttAttributesRequestProtoSqlIntegrationTest.java → application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/sql/MqttAttributesRequestProtoIntegrationTest.java

@ -19,5 +19,5 @@ import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.transport.mqtt.attributes.request.AbstractMqttAttributesRequestProtoIntegrationTest; import org.thingsboard.server.transport.mqtt.attributes.request.AbstractMqttAttributesRequestProtoIntegrationTest;
@DaoSqlTest @DaoSqlTest
public class MqttAttributesRequestProtoSqlIntegrationTest extends AbstractMqttAttributesRequestProtoIntegrationTest { public class MqttAttributesRequestProtoIntegrationTest extends AbstractMqttAttributesRequestProtoIntegrationTest {
} }

69
application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/AbstractMqttAttributesUpdatesBackwardCompatibilityIntegrationTest.java

@ -0,0 +1,69 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.mqtt.attributes.updates;
import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Test;
import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.transport.mqtt.attributes.AbstractMqttAttributesIntegrationTest;
@Slf4j
public abstract class AbstractMqttAttributesUpdatesBackwardCompatibilityIntegrationTest extends AbstractMqttAttributesIntegrationTest {
@After
public void afterTest() throws Exception {
processAfterTest();
}
@Test
public void testSubscribeToAttributesUpdatesFromServerWithEnabledJsonCompatibility() throws Exception {
super.processBeforeTest("Test Subscribe to attribute updates", "Gateway Test Subscribe to attribute updates", TransportPayloadType.PROTOBUF, null, null, true, false);
processProtoTestSubscribeToAttributesUpdates(MqttTopics.DEVICE_ATTRIBUTES_TOPIC);
}
@Test
public void testSubscribeToAttributesUpdatesFromServerWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception {
super.processBeforeTest("Test Subscribe to attribute updates", "Gateway Test Subscribe to attribute updates", TransportPayloadType.PROTOBUF, null, null, true, true);
processJsonTestSubscribeToAttributesUpdates(MqttTopics.DEVICE_ATTRIBUTES_TOPIC);
}
@Test
public void testProtoSubscribeToAttributesUpdatesFromTheServerOnShortTopicWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception {
super.processBeforeTest("Test Subscribe to attribute updates", "Gateway Test Subscribe to attribute updates", TransportPayloadType.PROTOBUF, null, null, true, true);
processProtoTestSubscribeToAttributesUpdates(MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC);
}
@Test
public void testProtoSubscribeToAttributesUpdatesFromTheServerOnShortJsonTopicWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception {
super.processBeforeTest("Test Subscribe to attribute updates", "Gateway Test Subscribe to attribute updates", TransportPayloadType.PROTOBUF, null, null, true, true);
processJsonTestSubscribeToAttributesUpdates(MqttTopics.DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC);
}
@Test
public void testProtoSubscribeToAttributesUpdatesFromTheServerOnShortProtoTopicWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception {
super.processBeforeTest("Test Subscribe to attribute updates", "Gateway Test Subscribe to attribute updates", TransportPayloadType.PROTOBUF, null, null, true, true);
processProtoTestSubscribeToAttributesUpdates(MqttTopics.DEVICE_ATTRIBUTES_SHORT_PROTO_TOPIC);
}
@Test
public void testProtoSubscribeToAttributesUpdatesFromTheServerGatewayWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception {
super.processBeforeTest("Test Subscribe to attribute updates", "Gateway Test Subscribe to attribute updates", TransportPayloadType.PROTOBUF, null, null, true, false);
processProtoGatewayTestSubscribeToAttributesUpdates();
}
}

132
application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/AbstractMqttAttributesUpdatesIntegrationTest.java

@ -15,40 +15,17 @@
*/ */
package org.thingsboard.server.transport.mqtt.attributes.updates; package org.thingsboard.server.transport.mqtt.attributes.updates;
import com.google.protobuf.InvalidProtocolBufferException;
import io.netty.handler.codec.mqtt.MqttQoS;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.transport.mqtt.attributes.AbstractMqttAttributesIntegrationTest; import org.thingsboard.server.transport.mqtt.attributes.AbstractMqttAttributesIntegrationTest;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Slf4j @Slf4j
public abstract class AbstractMqttAttributesUpdatesIntegrationTest extends AbstractMqttAttributesIntegrationTest { public abstract class AbstractMqttAttributesUpdatesIntegrationTest extends AbstractMqttAttributesIntegrationTest {
private static final String RESPONSE_ATTRIBUTES_PAYLOAD_DELETED = "{\"deleted\":[\"attribute5\"]}";
private static String getResponseGatewayAttributesUpdatedPayload() {
return "{\"device\":\"" + "Gateway Device Subscribe to attribute updates" + "\"," +
"\"data\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}}";
}
private static String getResponseGatewayAttributesDeletedPayload() {
return "{\"device\":\"" + "Gateway Device Subscribe to attribute updates" + "\",\"data\":{\"deleted\":[\"attribute5\"]}}";
}
@Before @Before
public void beforeTest() throws Exception { public void beforeTest() throws Exception {
processBeforeTest("Test Subscribe to attribute updates", "Gateway Test Subscribe to attribute updates", TransportPayloadType.JSON, null, null); processBeforeTest("Test Subscribe to attribute updates", "Gateway Test Subscribe to attribute updates", TransportPayloadType.JSON, null, null);
@ -60,116 +37,23 @@ public abstract class AbstractMqttAttributesUpdatesIntegrationTest extends Abstr
} }
@Test @Test
public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception { public void testJsonSubscribeToAttributesUpdatesFromTheServer() throws Exception {
processTestSubscribeToAttributesUpdates(MqttTopics.DEVICE_ATTRIBUTES_TOPIC); processJsonTestSubscribeToAttributesUpdates(MqttTopics.DEVICE_ATTRIBUTES_TOPIC);
} }
@Test @Test
public void testSubscribeToAttributesUpdatesFromTheServerOnShortTopic() throws Exception { public void testJsonSubscribeToAttributesUpdatesFromTheServerOnShortTopic() throws Exception {
processTestSubscribeToAttributesUpdates(MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC); processJsonTestSubscribeToAttributesUpdates(MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC);
} }
@Test @Test
public void testSubscribeToAttributesUpdatesFromTheServerOnShortJsonTopic() throws Exception { public void testJsonSubscribeToAttributesUpdatesFromTheServerOnShortJsonTopic() throws Exception {
processTestSubscribeToAttributesUpdates(MqttTopics.DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC); processJsonTestSubscribeToAttributesUpdates(MqttTopics.DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC);
} }
@Test @Test
public void testSubscribeToAttributesUpdatesFromTheServerGateway() throws Exception { public void testJsonSubscribeToAttributesUpdatesFromTheServerGateway() throws Exception {
processGatewayTestSubscribeToAttributesUpdates(); processJsonGatewayTestSubscribeToAttributesUpdates();
}
protected void processTestSubscribeToAttributesUpdates(String attrSubTopic) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(accessToken);
TestMqttCallback onUpdateCallback = getTestMqttCallback();
client.setCallback(onUpdateCallback);
client.subscribe(attrSubTopic, MqttQoS.AT_MOST_ONCE.value());
Thread.sleep(1000);
doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
onUpdateCallback.getLatch().await(3, TimeUnit.SECONDS);
validateUpdateAttributesResponse(onUpdateCallback);
TestMqttCallback onDeleteCallback = getTestMqttCallback();
client.setCallback(onDeleteCallback);
doDelete("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/SHARED_SCOPE?keys=attribute5", String.class);
onDeleteCallback.getLatch().await(3, TimeUnit.SECONDS);
validateDeleteAttributesResponse(onDeleteCallback);
}
protected void validateUpdateAttributesResponse(TestMqttCallback callback) throws InvalidProtocolBufferException {
assertNotNull(callback.getPayloadBytes());
String response = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8);
assertEquals(JacksonUtil.toJsonNode(POST_ATTRIBUTES_PAYLOAD), JacksonUtil.toJsonNode(response));
}
protected void validateDeleteAttributesResponse(TestMqttCallback callback) throws InvalidProtocolBufferException {
assertNotNull(callback.getPayloadBytes());
String response = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8);
assertEquals(JacksonUtil.toJsonNode(RESPONSE_ATTRIBUTES_PAYLOAD_DELETED), JacksonUtil.toJsonNode(response));
} }
protected void processGatewayTestSubscribeToAttributesUpdates() throws Exception {
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
TestMqttCallback onUpdateCallback = getTestMqttCallback();
client.setCallback(onUpdateCallback);
Device device = new Device();
device.setName("Gateway Device Subscribe to attribute updates");
device.setType("default");
byte[] connectPayloadBytes = getConnectPayloadBytes();
publishMqttMsg(client, connectPayloadBytes, MqttTopics.GATEWAY_CONNECT_TOPIC);
Device savedDevice = doExecuteWithRetriesAndInterval(() -> doGet("/api/tenant/devices?deviceName=" + "Gateway Device Subscribe to attribute updates", Device.class),
20,
100);
assertNotNull(savedDevice);
client.subscribe(MqttTopics.GATEWAY_ATTRIBUTES_TOPIC, MqttQoS.AT_MOST_ONCE.value());
Thread.sleep(1000);
doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
onUpdateCallback.getLatch().await(3, TimeUnit.SECONDS);
validateGatewayUpdateAttributesResponse(onUpdateCallback);
TestMqttCallback onDeleteCallback = getTestMqttCallback();
client.setCallback(onDeleteCallback);
doDelete("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/SHARED_SCOPE?keys=attribute5", String.class);
onDeleteCallback.getLatch().await(3, TimeUnit.SECONDS);
validateGatewayDeleteAttributesResponse(onDeleteCallback);
}
protected void validateGatewayUpdateAttributesResponse(TestMqttCallback callback) throws InvalidProtocolBufferException {
assertNotNull(callback.getPayloadBytes());
String s = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8);
assertEquals(getResponseGatewayAttributesUpdatedPayload(), s);
}
protected void validateGatewayDeleteAttributesResponse(TestMqttCallback callback) throws InvalidProtocolBufferException {
assertNotNull(callback.getPayloadBytes());
String s = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8);
assertEquals(s, getResponseGatewayAttributesDeletedPayload());
}
protected byte[] getConnectPayloadBytes() {
String connectPayload = "{\"device\": \"Gateway Device Subscribe to attribute updates\", \"type\": \"" + TransportPayloadType.JSON.name() + "\"}";
return connectPayload.getBytes();
}
} }

20
application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/AbstractMqttAttributesUpdatesJsonIntegrationTest.java

@ -20,9 +20,11 @@ import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.transport.mqtt.attributes.AbstractMqttAttributesIntegrationTest;
@Slf4j @Slf4j
public abstract class AbstractMqttAttributesUpdatesJsonIntegrationTest extends AbstractMqttAttributesUpdatesIntegrationTest { public abstract class AbstractMqttAttributesUpdatesJsonIntegrationTest extends AbstractMqttAttributesIntegrationTest {
@Before @Before
public void beforeTest() throws Exception { public void beforeTest() throws Exception {
@ -35,22 +37,22 @@ public abstract class AbstractMqttAttributesUpdatesJsonIntegrationTest extends A
} }
@Test @Test
public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception { public void testJsonSubscribeToAttributesUpdatesFromTheServer() throws Exception {
super.testSubscribeToAttributesUpdatesFromTheServer(); processJsonTestSubscribeToAttributesUpdates(MqttTopics.DEVICE_ATTRIBUTES_TOPIC);
} }
@Test @Test
public void testSubscribeToAttributesUpdatesFromTheServerOnShortTopic() throws Exception { public void testJsonSubscribeToAttributesUpdatesFromTheServerOnShortTopic() throws Exception {
super.testSubscribeToAttributesUpdatesFromTheServerOnShortTopic(); processJsonTestSubscribeToAttributesUpdates(MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC);
} }
@Test @Test
public void testSubscribeToAttributesUpdatesFromTheServerOnShortJsonTopic() throws Exception { public void testJsonSubscribeToAttributesUpdatesFromTheServerOnShortJsonTopic() throws Exception {
super.testSubscribeToAttributesUpdatesFromTheServerOnShortJsonTopic(); processJsonTestSubscribeToAttributesUpdates(MqttTopics.DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC);
} }
@Test @Test
public void testSubscribeToAttributesUpdatesFromTheServerGateway() throws Exception { public void testJsonSubscribeToAttributesUpdatesFromTheServerGateway() throws Exception {
processGatewayTestSubscribeToAttributesUpdates(); processJsonGatewayTestSubscribeToAttributesUpdates();
} }
} }

113
application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java

@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.gen.transport.TransportApiProtos; import org.thingsboard.server.gen.transport.TransportApiProtos;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.transport.mqtt.attributes.AbstractMqttAttributesIntegrationTest;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -33,7 +34,7 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@Slf4j @Slf4j
public abstract class AbstractMqttAttributesUpdatesProtoIntegrationTest extends AbstractMqttAttributesUpdatesIntegrationTest { public abstract class AbstractMqttAttributesUpdatesProtoIntegrationTest extends AbstractMqttAttributesIntegrationTest {
@Before @Before
public void beforeTest() throws Exception { public void beforeTest() throws Exception {
@ -46,116 +47,28 @@ public abstract class AbstractMqttAttributesUpdatesProtoIntegrationTest extends
} }
@Test @Test
public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception { public void testProtoSubscribeToAttributesUpdatesFromTheServer() throws Exception {
super.testSubscribeToAttributesUpdatesFromTheServer(); processProtoTestSubscribeToAttributesUpdates(MqttTopics.DEVICE_ATTRIBUTES_TOPIC);
} }
@Test @Test
public void testSubscribeToAttributesUpdatesFromTheServerOnShortTopic() throws Exception { public void testProtoSubscribeToAttributesUpdatesFromTheServerOnShortTopic() throws Exception {
super.testSubscribeToAttributesUpdatesFromTheServerOnShortTopic(); processProtoTestSubscribeToAttributesUpdates(MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC);
} }
@Test @Test
public void testSubscribeToAttributesUpdatesFromTheServerOnShortJsonTopic() throws Exception {} public void testProtoSubscribeToAttributesUpdatesFromTheServerOnShortJsonTopic() throws Exception {
processJsonTestSubscribeToAttributesUpdates(MqttTopics.DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC);
@Test
public void testSubscribeToAttributesUpdatesFromTheServerOnShortProtoTopic() throws Exception {
processTestSubscribeToAttributesUpdates(MqttTopics.DEVICE_ATTRIBUTES_SHORT_PROTO_TOPIC);
} }
@Test @Test
public void testSubscribeToAttributesUpdatesFromTheServerGateway() throws Exception { public void testProtoSubscribeToAttributesUpdatesFromTheServerOnShortProtoTopic() throws Exception {
processGatewayTestSubscribeToAttributesUpdates(); processProtoTestSubscribeToAttributesUpdates(MqttTopics.DEVICE_ATTRIBUTES_SHORT_PROTO_TOPIC);
} }
protected void validateUpdateAttributesResponse(TestMqttCallback callback) throws InvalidProtocolBufferException { @Test
assertNotNull(callback.getPayloadBytes()); public void testProtoSubscribeToAttributesUpdatesFromTheServerGateway() throws Exception {
TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder(); processProtoGatewayTestSubscribeToAttributesUpdates();
List<TransportProtos.TsKvProto> tsKvProtoList = getTsKvProtoList();
attributeUpdateNotificationMsgBuilder.addAllSharedUpdated(tsKvProtoList);
TransportProtos.AttributeUpdateNotificationMsg expectedAttributeUpdateNotificationMsg = attributeUpdateNotificationMsgBuilder.build();
TransportProtos.AttributeUpdateNotificationMsg actualAttributeUpdateNotificationMsg = TransportProtos.AttributeUpdateNotificationMsg.parseFrom(callback.getPayloadBytes());
List<TransportProtos.KeyValueProto> actualSharedUpdatedList = actualAttributeUpdateNotificationMsg.getSharedUpdatedList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
List<TransportProtos.KeyValueProto> expectedSharedUpdatedList = expectedAttributeUpdateNotificationMsg.getSharedUpdatedList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
assertEquals(expectedSharedUpdatedList.size(), actualSharedUpdatedList.size());
assertTrue(actualSharedUpdatedList.containsAll(expectedSharedUpdatedList));
}
protected void validateDeleteAttributesResponse(TestMqttCallback callback) throws InvalidProtocolBufferException {
assertNotNull(callback.getPayloadBytes());
TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder();
attributeUpdateNotificationMsgBuilder.addSharedDeleted("attribute5");
TransportProtos.AttributeUpdateNotificationMsg expectedAttributeUpdateNotificationMsg = attributeUpdateNotificationMsgBuilder.build();
TransportProtos.AttributeUpdateNotificationMsg actualAttributeUpdateNotificationMsg = TransportProtos.AttributeUpdateNotificationMsg.parseFrom(callback.getPayloadBytes());
assertEquals(expectedAttributeUpdateNotificationMsg.getSharedDeletedList().size(), actualAttributeUpdateNotificationMsg.getSharedDeletedList().size());
assertEquals("attribute5", actualAttributeUpdateNotificationMsg.getSharedDeletedList().get(0));
}
protected void validateGatewayUpdateAttributesResponse(TestMqttCallback callback) throws InvalidProtocolBufferException {
assertNotNull(callback.getPayloadBytes());
TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder();
List<TransportProtos.TsKvProto> tsKvProtoList = getTsKvProtoList();
attributeUpdateNotificationMsgBuilder.addAllSharedUpdated(tsKvProtoList);
TransportProtos.AttributeUpdateNotificationMsg expectedAttributeUpdateNotificationMsg = attributeUpdateNotificationMsgBuilder.build();
TransportApiProtos.GatewayAttributeUpdateNotificationMsg.Builder gatewayAttributeUpdateNotificationMsgBuilder = TransportApiProtos.GatewayAttributeUpdateNotificationMsg.newBuilder();
gatewayAttributeUpdateNotificationMsgBuilder.setDeviceName("Gateway Device Subscribe to attribute updates");
gatewayAttributeUpdateNotificationMsgBuilder.setNotificationMsg(expectedAttributeUpdateNotificationMsg);
TransportApiProtos.GatewayAttributeUpdateNotificationMsg expectedGatewayAttributeUpdateNotificationMsg = gatewayAttributeUpdateNotificationMsgBuilder.build();
TransportApiProtos.GatewayAttributeUpdateNotificationMsg actualGatewayAttributeUpdateNotificationMsg = TransportApiProtos.GatewayAttributeUpdateNotificationMsg.parseFrom(callback.getPayloadBytes());
assertEquals(expectedGatewayAttributeUpdateNotificationMsg.getDeviceName(), actualGatewayAttributeUpdateNotificationMsg.getDeviceName());
List<TransportProtos.KeyValueProto> actualSharedUpdatedList = actualGatewayAttributeUpdateNotificationMsg.getNotificationMsg().getSharedUpdatedList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
List<TransportProtos.KeyValueProto> expectedSharedUpdatedList = expectedGatewayAttributeUpdateNotificationMsg.getNotificationMsg().getSharedUpdatedList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
assertEquals(expectedSharedUpdatedList.size(), actualSharedUpdatedList.size());
assertTrue(actualSharedUpdatedList.containsAll(expectedSharedUpdatedList));
}
protected void validateGatewayDeleteAttributesResponse(TestMqttCallback callback) throws InvalidProtocolBufferException {
assertNotNull(callback.getPayloadBytes());
TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder();
attributeUpdateNotificationMsgBuilder.addSharedDeleted("attribute5");
TransportProtos.AttributeUpdateNotificationMsg attributeUpdateNotificationMsg = attributeUpdateNotificationMsgBuilder.build();
TransportApiProtos.GatewayAttributeUpdateNotificationMsg.Builder gatewayAttributeUpdateNotificationMsgBuilder = TransportApiProtos.GatewayAttributeUpdateNotificationMsg.newBuilder();
gatewayAttributeUpdateNotificationMsgBuilder.setDeviceName("Gateway Device Subscribe to attribute updates");
gatewayAttributeUpdateNotificationMsgBuilder.setNotificationMsg(attributeUpdateNotificationMsg);
TransportApiProtos.GatewayAttributeUpdateNotificationMsg expectedGatewayAttributeUpdateNotificationMsg = gatewayAttributeUpdateNotificationMsgBuilder.build();
TransportApiProtos.GatewayAttributeUpdateNotificationMsg actualGatewayAttributeUpdateNotificationMsg = TransportApiProtos.GatewayAttributeUpdateNotificationMsg.parseFrom(callback.getPayloadBytes());
assertEquals(expectedGatewayAttributeUpdateNotificationMsg.getDeviceName(), actualGatewayAttributeUpdateNotificationMsg.getDeviceName());
TransportProtos.AttributeUpdateNotificationMsg expectedAttributeUpdateNotificationMsg = expectedGatewayAttributeUpdateNotificationMsg.getNotificationMsg();
TransportProtos.AttributeUpdateNotificationMsg actualAttributeUpdateNotificationMsg = actualGatewayAttributeUpdateNotificationMsg.getNotificationMsg();
assertEquals(expectedAttributeUpdateNotificationMsg.getSharedDeletedList().size(), actualAttributeUpdateNotificationMsg.getSharedDeletedList().size());
assertEquals("attribute5", actualAttributeUpdateNotificationMsg.getSharedDeletedList().get(0));
}
protected byte[] getConnectPayloadBytes() {
TransportApiProtos.ConnectMsg connectProto = getConnectProto();
return connectProto.toByteArray();
}
private TransportApiProtos.ConnectMsg getConnectProto() {
TransportApiProtos.ConnectMsg.Builder builder = TransportApiProtos.ConnectMsg.newBuilder();
builder.setDeviceName("Gateway Device Subscribe to attribute updates");
builder.setDeviceType(TransportPayloadType.PROTOBUF.name());
return builder.build();
} }
} }

23
application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/sql/MqttAttributesUpdatesBackwardCompatibilityIntegrationTest.java

@ -0,0 +1,23 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.mqtt.attributes.updates.sql;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.transport.mqtt.attributes.updates.AbstractMqttAttributesUpdatesBackwardCompatibilityIntegrationTest;
@DaoSqlTest
public class MqttAttributesUpdatesBackwardCompatibilityIntegrationTest extends AbstractMqttAttributesUpdatesBackwardCompatibilityIntegrationTest {
}

2
application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlIntegrationTest.java → application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/sql/MqttAttributesUpdatesIntegrationTest.java

@ -19,5 +19,5 @@ import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.transport.mqtt.attributes.updates.AbstractMqttAttributesUpdatesIntegrationTest; import org.thingsboard.server.transport.mqtt.attributes.updates.AbstractMqttAttributesUpdatesIntegrationTest;
@DaoSqlTest @DaoSqlTest
public class MqttAttributesUpdatesSqlIntegrationTest extends AbstractMqttAttributesUpdatesIntegrationTest { public class MqttAttributesUpdatesIntegrationTest extends AbstractMqttAttributesUpdatesIntegrationTest {
} }

2
application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlJsonIntegrationTest.java → application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/sql/MqttAttributesUpdatesJsonIntegrationTest.java

@ -19,5 +19,5 @@ import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.transport.mqtt.attributes.updates.AbstractMqttAttributesUpdatesJsonIntegrationTest; import org.thingsboard.server.transport.mqtt.attributes.updates.AbstractMqttAttributesUpdatesJsonIntegrationTest;
@DaoSqlTest @DaoSqlTest
public class MqttAttributesUpdatesSqlJsonIntegrationTest extends AbstractMqttAttributesUpdatesJsonIntegrationTest { public class MqttAttributesUpdatesJsonIntegrationTest extends AbstractMqttAttributesUpdatesJsonIntegrationTest {
} }

2
application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlProtoIntegrationTest.java → application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/sql/MqttAttributesUpdatesProtoIntegrationTest.java

@ -19,5 +19,5 @@ import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.transport.mqtt.attributes.updates.AbstractMqttAttributesUpdatesProtoIntegrationTest; import org.thingsboard.server.transport.mqtt.attributes.updates.AbstractMqttAttributesUpdatesProtoIntegrationTest;
@DaoSqlTest @DaoSqlTest
public class MqttAttributesUpdatesSqlProtoIntegrationTest extends AbstractMqttAttributesUpdatesProtoIntegrationTest { public class MqttAttributesUpdatesProtoIntegrationTest extends AbstractMqttAttributesUpdatesProtoIntegrationTest {
} }

50
application/src/test/java/org/thingsboard/server/transport/mqtt/claim/AbstractMqttClaimBackwardCompatibilityDeviceTest.java

@ -0,0 +1,50 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.mqtt.claim;
import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.server.common.data.TransportPayloadType;
@Slf4j
public abstract class AbstractMqttClaimBackwardCompatibilityDeviceTest extends AbstractMqttClaimDeviceTest {
@Before
public void beforeTest() throws Exception {
processBeforeTest("Test Claim device", "Test Claim gateway", TransportPayloadType.PROTOBUF, null, null, true, true);
createCustomerAndUser();
}
@After
public void afterTest() throws Exception { super.afterTest(); }
@Test
public void testGatewayClaimingDevice() throws Exception {
processTestGatewayClaimingDevice("Test claiming gateway device Proto", false);
}
@Test
public void testGatewayClaimingDeviceWithoutSecretAndDuration() throws Exception {
processTestGatewayClaimingDevice("Test claiming gateway device empty payload Proto", true);
}
protected void processTestGatewayClaimingDevice(String deviceName, boolean emptyPayload) throws Exception {
processProtoTestGatewayClaimDevice(deviceName, emptyPayload);
}
}

33
application/src/test/java/org/thingsboard/server/transport/mqtt/claim/AbstractMqttClaimDeviceTest.java

@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.device.claim.ClaimResponse; import org.thingsboard.server.dao.device.claim.ClaimResponse;
import org.thingsboard.server.dao.device.claim.ClaimResult; import org.thingsboard.server.dao.device.claim.ClaimResult;
import org.thingsboard.server.gen.transport.TransportApiProtos;
import org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest; import org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@ -202,4 +203,36 @@ public abstract class AbstractMqttClaimDeviceTest extends AbstractMqttIntegratio
validateGatewayClaimResponse(deviceName, emptyPayload, client, failurePayloadBytes, payloadBytes); validateGatewayClaimResponse(deviceName, emptyPayload, client, failurePayloadBytes, payloadBytes);
} }
protected void processProtoTestGatewayClaimDevice(String deviceName, boolean emptyPayload) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
byte[] failurePayloadBytes;
byte[] payloadBytes;
if (emptyPayload) {
payloadBytes = getGatewayClaimMsg(deviceName, 0, emptyPayload).toByteArray();
} else {
payloadBytes = getGatewayClaimMsg(deviceName, 60000, emptyPayload).toByteArray();
}
failurePayloadBytes = getGatewayClaimMsg(deviceName, 1, emptyPayload).toByteArray();
validateGatewayClaimResponse(deviceName, emptyPayload, client, failurePayloadBytes, payloadBytes);
}
private TransportApiProtos.GatewayClaimMsg getGatewayClaimMsg(String deviceName, long duration, boolean emptyPayload) {
TransportApiProtos.GatewayClaimMsg.Builder gatewayClaimMsgBuilder = TransportApiProtos.GatewayClaimMsg.newBuilder();
TransportApiProtos.ClaimDeviceMsg.Builder claimDeviceMsgBuilder = TransportApiProtos.ClaimDeviceMsg.newBuilder();
TransportApiProtos.ClaimDevice.Builder claimDeviceBuilder = TransportApiProtos.ClaimDevice.newBuilder();
if (!emptyPayload) {
claimDeviceBuilder.setSecretKey("value");
}
if (duration > 0) {
claimDeviceBuilder.setDurationMs(duration);
}
TransportApiProtos.ClaimDevice claimDevice = claimDeviceBuilder.build();
claimDeviceMsgBuilder.setClaimRequest(claimDevice);
claimDeviceMsgBuilder.setDeviceName(deviceName);
TransportApiProtos.ClaimDeviceMsg claimDeviceMsg = claimDeviceMsgBuilder.build();
gatewayClaimMsgBuilder.addMsg(claimDeviceMsg);
return gatewayClaimMsgBuilder.build();
}
} }

30
application/src/test/java/org/thingsboard/server/transport/mqtt/claim/AbstractMqttClaimProtoDeviceTest.java

@ -68,35 +68,7 @@ public abstract class AbstractMqttClaimProtoDeviceTest extends AbstractMqttClaim
} }
protected void processTestGatewayClaimingDevice(String deviceName, boolean emptyPayload) throws Exception { protected void processTestGatewayClaimingDevice(String deviceName, boolean emptyPayload) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken); processProtoTestGatewayClaimDevice(deviceName, emptyPayload);
byte[] failurePayloadBytes;
byte[] payloadBytes;
if (emptyPayload) {
payloadBytes = getGatewayClaimMsg(deviceName, 0, emptyPayload).toByteArray();
} else {
payloadBytes = getGatewayClaimMsg(deviceName, 60000, emptyPayload).toByteArray();
}
failurePayloadBytes = getGatewayClaimMsg(deviceName, 1, emptyPayload).toByteArray();
validateGatewayClaimResponse(deviceName, emptyPayload, client, failurePayloadBytes, payloadBytes);
}
private TransportApiProtos.GatewayClaimMsg getGatewayClaimMsg(String deviceName, long duration, boolean emptyPayload) {
TransportApiProtos.GatewayClaimMsg.Builder gatewayClaimMsgBuilder = TransportApiProtos.GatewayClaimMsg.newBuilder();
TransportApiProtos.ClaimDeviceMsg.Builder claimDeviceMsgBuilder = TransportApiProtos.ClaimDeviceMsg.newBuilder();
TransportApiProtos.ClaimDevice.Builder claimDeviceBuilder = TransportApiProtos.ClaimDevice.newBuilder();
if (!emptyPayload) {
claimDeviceBuilder.setSecretKey("value");
}
if (duration > 0) {
claimDeviceBuilder.setDurationMs(duration);
}
TransportApiProtos.ClaimDevice claimDevice = claimDeviceBuilder.build();
claimDeviceMsgBuilder.setClaimRequest(claimDevice);
claimDeviceMsgBuilder.setDeviceName(deviceName);
TransportApiProtos.ClaimDeviceMsg claimDeviceMsg = claimDeviceMsgBuilder.build();
gatewayClaimMsgBuilder.addMsg(claimDeviceMsg);
return gatewayClaimMsgBuilder.build();
} }
private TransportApiProtos.ClaimDevice getClaimDevice(long duration, boolean emptyPayload) { private TransportApiProtos.ClaimDevice getClaimDevice(long duration, boolean emptyPayload) {

24
application/src/test/java/org/thingsboard/server/transport/mqtt/claim/sql/MqttClaimDeviceBackwardCompatibilityTest.java

@ -0,0 +1,24 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.mqtt.claim.sql;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.transport.mqtt.claim.AbstractMqttClaimBackwardCompatibilityDeviceTest;
import org.thingsboard.server.transport.mqtt.claim.AbstractMqttClaimDeviceTest;
@DaoSqlTest
public class MqttClaimDeviceBackwardCompatibilityTest extends AbstractMqttClaimBackwardCompatibilityDeviceTest {
}

2
application/src/test/java/org/thingsboard/server/transport/mqtt/claim/sql/MqttClaimDeviceJsonSqlTest.java → application/src/test/java/org/thingsboard/server/transport/mqtt/claim/sql/MqttClaimDeviceJsonTest.java

@ -19,5 +19,5 @@ import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.transport.mqtt.claim.AbstractMqttClaimJsonDeviceTest; import org.thingsboard.server.transport.mqtt.claim.AbstractMqttClaimJsonDeviceTest;
@DaoSqlTest @DaoSqlTest
public class MqttClaimDeviceJsonSqlTest extends AbstractMqttClaimJsonDeviceTest { public class MqttClaimDeviceJsonTest extends AbstractMqttClaimJsonDeviceTest {
} }

2
application/src/test/java/org/thingsboard/server/transport/mqtt/claim/sql/MqttClaimDeviceProtoSqlTest.java → application/src/test/java/org/thingsboard/server/transport/mqtt/claim/sql/MqttClaimDeviceProtoTest.java

@ -19,5 +19,5 @@ import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.transport.mqtt.claim.AbstractMqttClaimProtoDeviceTest; import org.thingsboard.server.transport.mqtt.claim.AbstractMqttClaimProtoDeviceTest;
@DaoSqlTest @DaoSqlTest
public class MqttClaimDeviceProtoSqlTest extends AbstractMqttClaimProtoDeviceTest { public class MqttClaimDeviceProtoTest extends AbstractMqttClaimProtoDeviceTest {
} }

2
application/src/test/java/org/thingsboard/server/transport/mqtt/claim/sql/MqttClaimDeviceSqlTest.java → application/src/test/java/org/thingsboard/server/transport/mqtt/claim/sql/MqttClaimDeviceTest.java

@ -19,5 +19,5 @@ import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.transport.mqtt.claim.AbstractMqttClaimDeviceTest; import org.thingsboard.server.transport.mqtt.claim.AbstractMqttClaimDeviceTest;
@DaoSqlTest @DaoSqlTest
public class MqttClaimDeviceSqlTest extends AbstractMqttClaimDeviceTest { public class MqttClaimDeviceTest extends AbstractMqttClaimDeviceTest {
} }

14
application/src/test/java/org/thingsboard/server/transport/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java

@ -94,7 +94,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn
protected void processTestProvisioningDisabledDevice() throws Exception { protected void processTestProvisioningDisabledDevice() throws Exception {
super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED); super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false);
byte[] result = createMqttClientAndPublish().getPayloadBytes(); byte[] result = createMqttClientAndPublish().getPayloadBytes();
JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject();
Assert.assertEquals("Provision data was not found!", response.get("errorMsg").getAsString()); Assert.assertEquals("Provision data was not found!", response.get("errorMsg").getAsString());
@ -103,7 +103,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn
protected void processTestProvisioningCreateNewDeviceWithoutCredentials() throws Exception { protected void processTestProvisioningCreateNewDeviceWithoutCredentials() throws Exception {
super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES); super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, false, false);
byte[] result = createMqttClientAndPublish().getPayloadBytes(); byte[] result = createMqttClientAndPublish().getPayloadBytes();
JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject();
@ -119,7 +119,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn
protected void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception { protected void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception {
super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES); super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, false, false);
String requestCredentials = ",\"credentialsType\": \"ACCESS_TOKEN\",\"token\": \"test_token\""; String requestCredentials = ",\"credentialsType\": \"ACCESS_TOKEN\",\"token\": \"test_token\"";
byte[] result = createMqttClientAndPublish(requestCredentials).getPayloadBytes(); byte[] result = createMqttClientAndPublish(requestCredentials).getPayloadBytes();
JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject();
@ -138,7 +138,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn
protected void processTestProvisioningCreateNewDeviceWithCert() throws Exception { protected void processTestProvisioningCreateNewDeviceWithCert() throws Exception {
super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES); super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, false, false);
String requestCredentials = ",\"credentialsType\": \"X509_CERTIFICATE\",\"hash\": \"testHash\""; String requestCredentials = ",\"credentialsType\": \"X509_CERTIFICATE\",\"hash\": \"testHash\"";
byte[] result = createMqttClientAndPublish(requestCredentials).getPayloadBytes(); byte[] result = createMqttClientAndPublish(requestCredentials).getPayloadBytes();
JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject();
@ -163,7 +163,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn
protected void processTestProvisioningCreateNewDeviceWithMqttBasic() throws Exception { protected void processTestProvisioningCreateNewDeviceWithMqttBasic() throws Exception {
super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES); super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, false, false);
String requestCredentials = ",\"credentialsType\": \"MQTT_BASIC\",\"clientId\": \"test_clientId\",\"username\": \"test_username\",\"password\": \"test_password\""; String requestCredentials = ",\"credentialsType\": \"MQTT_BASIC\",\"clientId\": \"test_clientId\",\"username\": \"test_username\",\"password\": \"test_password\"";
byte[] result = createMqttClientAndPublish(requestCredentials).getPayloadBytes(); byte[] result = createMqttClientAndPublish(requestCredentials).getPayloadBytes();
JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject();
@ -188,7 +188,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn
} }
protected void processTestProvisioningCheckPreProvisionedDevice() throws Exception { protected void processTestProvisioningCheckPreProvisionedDevice() throws Exception {
super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES); super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, false, false);
byte[] result = createMqttClientAndPublish().getPayloadBytes(); byte[] result = createMqttClientAndPublish().getPayloadBytes();
JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject();
@ -199,7 +199,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn
} }
protected void processTestProvisioningWithBadKeyDevice() throws Exception { protected void processTestProvisioningWithBadKeyDevice() throws Exception {
super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, "testProvisionKeyOrig", "testProvisionSecret", DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES); super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, "testProvisionKeyOrig", "testProvisionSecret", DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, false, false);
byte[] result = createMqttClientAndPublish().getPayloadBytes(); byte[] result = createMqttClientAndPublish().getPayloadBytes();
JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject();
Assert.assertEquals("Provision data was not found!", response.get("errorMsg").getAsString()); Assert.assertEquals("Provision data was not found!", response.get("errorMsg").getAsString());

14
application/src/test/java/org/thingsboard/server/transport/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java

@ -101,14 +101,14 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI
protected void processTestProvisioningDisabledDevice() throws Exception { protected void processTestProvisioningDisabledDevice() throws Exception {
super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED); super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false);
ProvisionDeviceResponseMsg result = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); ProvisionDeviceResponseMsg result = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes());
Assert.assertNotNull(result); Assert.assertNotNull(result);
Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), result.getStatus().toString()); Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), result.getStatus().toString());
} }
protected void processTestProvisioningCreateNewDeviceWithoutCredentials() throws Exception { protected void processTestProvisioningCreateNewDeviceWithoutCredentials() throws Exception {
super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES); super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, false, false);
ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes());
Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device");
@ -122,7 +122,7 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI
} }
protected void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception { protected void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception {
super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null,null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES); super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null,null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, false, false);
CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateDeviceTokenRequestMsg(ValidateDeviceTokenRequestMsg.newBuilder().setToken("test_token").build()).build(); CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateDeviceTokenRequestMsg(ValidateDeviceTokenRequestMsg.newBuilder().setToken("test_token").build()).build();
ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish(createTestsProvisionMessage(CredentialsType.ACCESS_TOKEN, requestCredentials)).getPayloadBytes()); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish(createTestsProvisionMessage(CredentialsType.ACCESS_TOKEN, requestCredentials)).getPayloadBytes());
@ -140,7 +140,7 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI
} }
protected void processTestProvisioningCreateNewDeviceWithCert() throws Exception { protected void processTestProvisioningCreateNewDeviceWithCert() throws Exception {
super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES); super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, false, false);
CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateDeviceX509CertRequestMsg(ValidateDeviceX509CertRequestMsg.newBuilder().setHash("testHash").build()).build(); CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateDeviceX509CertRequestMsg(ValidateDeviceX509CertRequestMsg.newBuilder().setHash("testHash").build()).build();
ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish(createTestsProvisionMessage(CredentialsType.X509_CERTIFICATE, requestCredentials)).getPayloadBytes()); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish(createTestsProvisionMessage(CredentialsType.X509_CERTIFICATE, requestCredentials)).getPayloadBytes());
@ -164,7 +164,7 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI
} }
protected void processTestProvisioningCreateNewDeviceWithMqttBasic() throws Exception { protected void processTestProvisioningCreateNewDeviceWithMqttBasic() throws Exception {
super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES); super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, false, false);
CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateBasicMqttCredRequestMsg( CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateBasicMqttCredRequestMsg(
ValidateBasicMqttCredRequestMsg.newBuilder() ValidateBasicMqttCredRequestMsg.newBuilder()
.setClientId("test_clientId") .setClientId("test_clientId")
@ -195,7 +195,7 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI
} }
protected void processTestProvisioningCheckPreProvisionedDevice() throws Exception { protected void processTestProvisioningCheckPreProvisionedDevice() throws Exception {
super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES); super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, false, false);
ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes());
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), savedDevice.getId()); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), savedDevice.getId());
@ -205,7 +205,7 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI
} }
protected void processTestProvisioningWithBadKeyDevice() throws Exception { protected void processTestProvisioningWithBadKeyDevice() throws Exception {
super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, "testProvisionKeyOrig", "testProvisionSecret", DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES); super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, "testProvisionKeyOrig", "testProvisionSecret", DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, false, false);
ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes());
Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.getStatus().toString()); Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.getStatus().toString());
} }

2
application/src/test/java/org/thingsboard/server/transport/mqtt/provision/sql/MqttProvisionDeviceJsonSqlTest.java → application/src/test/java/org/thingsboard/server/transport/mqtt/provision/sql/MqttProvisionDeviceJsonTest.java

@ -19,5 +19,5 @@ import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.transport.mqtt.provision.AbstractMqttProvisionJsonDeviceTest; import org.thingsboard.server.transport.mqtt.provision.AbstractMqttProvisionJsonDeviceTest;
@DaoSqlTest @DaoSqlTest
public class MqttProvisionDeviceJsonSqlTest extends AbstractMqttProvisionJsonDeviceTest { public class MqttProvisionDeviceJsonTest extends AbstractMqttProvisionJsonDeviceTest {
} }

2
application/src/test/java/org/thingsboard/server/transport/mqtt/provision/sql/MqttProvisionDeviceProtoSqlTest.java → application/src/test/java/org/thingsboard/server/transport/mqtt/provision/sql/MqttProvisionDeviceProtoTest.java

@ -19,5 +19,5 @@ import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.transport.mqtt.provision.AbstractMqttProvisionProtoDeviceTest; import org.thingsboard.server.transport.mqtt.provision.AbstractMqttProvisionProtoDeviceTest;
@DaoSqlTest @DaoSqlTest
public class MqttProvisionDeviceProtoSqlTest extends AbstractMqttProvisionProtoDeviceTest { public class MqttProvisionDeviceProtoTest extends AbstractMqttProvisionProtoDeviceTest {
} }

94
application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcBackwardCompatibilityIntegrationTest.java

@ -0,0 +1,94 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.mqtt.rpc;
import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.server.common.data.DeviceProfileProvisionType;
import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
@Slf4j
public abstract class AbstractMqttServerSideRpcBackwardCompatibilityIntegrationTest extends AbstractMqttServerSideRpcIntegrationTest {
@After
public void afterTest() throws Exception {
super.processAfterTest();
}
@Test
public void testServerMqttOneWayRpcWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception {
super.processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED, true, true);
processOneWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC);
}
@Test
public void testServerMqttOneWayRpcOnShortTopicWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception {
super.processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED, true, true);
processOneWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_TOPIC);
}
@Test
public void testServerMqttOneWayRpcOnShortProtoTopicWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception {
super.processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED, true, true);
processOneWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_PROTO_TOPIC);
}
@Test
public void testServerMqttTwoWayRpcWithEnabledJsonCompatibility() throws Exception {
super.processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED, true, false);
processProtoTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC);
}
@Test
public void testServerMqttTwoWayRpcWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception {
super.processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED, true, true);
processJsonTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC);
}
@Test
public void testServerMqttTwoWayRpcOnShortTopic() throws Exception {
super.processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED, true, true);
processProtoTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_TOPIC);
}
@Test
public void testServerMqttTwoWayRpcOnShortProtoTopicWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception {
super.processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED, true, true);
processProtoTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_PROTO_TOPIC);
}
@Test
public void testServerMqttTwoWayRpcOnShortJsonTopicWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception {
super.processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED, true, true);
processJsonTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_JSON_TOPIC);
}
@Test
public void testGatewayServerMqttOneWayRpcWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception {
super.processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED, true, true);
processProtoOneWayRpcTestGateway("Gateway Device OneWay RPC Proto");
}
@Test
public void testGatewayServerMqttTwoWayRpcWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception {
super.processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED, true, true);
processProtoTwoWayRpcTestGateway("Gateway Device TwoWay RPC Proto");
}
}

10
application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcDefaultIntegrationTest.java

@ -97,17 +97,17 @@ public abstract class AbstractMqttServerSideRpcDefaultIntegrationTest extends Ab
@Test @Test
public void testServerMqttTwoWayRpc() throws Exception { public void testServerMqttTwoWayRpc() throws Exception {
processTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC); processJsonTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC);
} }
@Test @Test
public void testServerMqttTwoWayRpcOnShortTopic() throws Exception { public void testServerMqttTwoWayRpcOnShortTopic() throws Exception {
processTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_TOPIC); processJsonTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_TOPIC);
} }
@Test @Test
public void testServerMqttTwoWayRpcOnShortJsonTopic() throws Exception { public void testServerMqttTwoWayRpcOnShortJsonTopic() throws Exception {
processTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_JSON_TOPIC); processJsonTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_JSON_TOPIC);
} }
@Test @Test
@ -117,12 +117,12 @@ public abstract class AbstractMqttServerSideRpcDefaultIntegrationTest extends Ab
@Test @Test
public void testGatewayServerMqttOneWayRpc() throws Exception { public void testGatewayServerMqttOneWayRpc() throws Exception {
processOneWayRpcTestGateway("Gateway Device OneWay RPC"); processJsonOneWayRpcTestGateway("Gateway Device OneWay RPC");
} }
@Test @Test
public void testGatewayServerMqttTwoWayRpc() throws Exception { public void testGatewayServerMqttTwoWayRpc() throws Exception {
processTwoWayRpcTestGateway("Gateway Device TwoWay RPC"); processJsonTwoWayRpcTestGateway("Gateway Device TwoWay RPC");
} }
} }

246
application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java

@ -17,8 +17,12 @@ package org.thingsboard.server.transport.mqtt.rpc;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import com.github.os72.protobuf.dynamic.DynamicSchema;
import com.google.protobuf.Descriptors;
import com.google.protobuf.DynamicMessage;
import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.InvalidProtocolBufferException;
import com.nimbusds.jose.util.StandardCharset; import com.nimbusds.jose.util.StandardCharset;
import com.squareup.wire.schema.internal.parser.ProtoFileElement;
import io.netty.handler.codec.mqtt.MqttQoS; import io.netty.handler.codec.mqtt.MqttQoS;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@ -31,18 +35,23 @@ import org.junit.Assert;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration;
import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration;
import org.thingsboard.server.gen.transport.TransportApiProtos;
import org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest; import org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/** /**
@ -51,7 +60,21 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
@Slf4j @Slf4j
public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractMqttIntegrationTest { public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractMqttIntegrationTest {
protected static final String DEVICE_RESPONSE = "{\"value1\":\"A\",\"value2\":\"B\"}"; protected static final String RPC_REQUEST_PROTO_SCHEMA = "syntax =\"proto3\";\n" +
"package rpc;\n" +
"\n" +
"message RpcRequestMsg {\n" +
" optional string method = 1;\n" +
" optional int32 requestId = 2;\n" +
" Params params = 3;\n" +
"\n" +
" message Params {\n" +
" optional string pin = 1;\n" +
" optional int32 value = 2;\n" +
" }\n" +
"}";
private static final String DEVICE_RESPONSE = "{\"value1\":\"A\",\"value2\":\"B\"}";
protected Long asyncContextTimeoutToUseRpcPlugin; protected Long asyncContextTimeoutToUseRpcPlugin;
@ -64,7 +87,7 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractM
MqttAsyncClient client = getMqttAsyncClient(accessToken); MqttAsyncClient client = getMqttAsyncClient(accessToken);
CountDownLatch latch = new CountDownLatch(1); CountDownLatch latch = new CountDownLatch(1);
TestMqttCallback callback = new TestMqttCallback(client, latch); TestOneWayMqttCallback callback = new TestOneWayMqttCallback(client, latch);
client.setCallback(callback); client.setCallback(callback);
client.subscribe(rpcSubTopic, MqttQoS.AT_MOST_ONCE.value()); client.subscribe(rpcSubTopic, MqttQoS.AT_MOST_ONCE.value());
@ -79,19 +102,19 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractM
assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS()); assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS());
} }
protected void processOneWayRpcTestGateway(String deviceName) throws Exception { protected void processJsonOneWayRpcTestGateway(String deviceName) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken); MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
String payload = "{\"device\":\"" + deviceName + "\"}"; String payload = "{\"device\":\"" + deviceName + "\"}";
byte[] payloadBytes = payload.getBytes(); byte[] payloadBytes = payload.getBytes();
validateOneWayRpcGatewayResponse(deviceName, client, payloadBytes); validateOneWayRpcGatewayResponse(deviceName, client, payloadBytes);
} }
protected void processTwoWayRpcTest(String rpcSubTopic) throws Exception { protected void processJsonTwoWayRpcTest(String rpcSubTopic) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(accessToken); MqttAsyncClient client = getMqttAsyncClient(accessToken);
client.subscribe(rpcSubTopic, 1); client.subscribe(rpcSubTopic, 1);
CountDownLatch latch = new CountDownLatch(1); CountDownLatch latch = new CountDownLatch(1);
TestMqttCallback callback = new TestMqttCallback(client, latch); TestJsonMqttCallback callback = new TestJsonMqttCallback(client, latch);
client.setCallback(callback); client.setCallback(callback);
Thread.sleep(1000); Thread.sleep(1000);
@ -105,6 +128,46 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractM
Assert.assertEquals(expected, result); Assert.assertEquals(expected, result);
} }
protected void processProtoTwoWayRpcTest(String rpcSubTopic) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(accessToken);
client.subscribe(rpcSubTopic, 1);
CountDownLatch latch = new CountDownLatch(1);
TestProtoMqttCallback callback = new TestProtoMqttCallback(client, latch);
client.setCallback(callback);
Thread.sleep(1000);
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"26\",\"value\": 1}}";
String deviceId = savedDevice.getId().getId().toString();
String result = doPostAsync("/api/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk());
String expected = "{\"payload\":\"{\\\"value1\\\":\\\"A\\\",\\\"value2\\\":\\\"B\\\"}\"}";
latch.await(3, TimeUnit.SECONDS);
Assert.assertEquals(expected, result);
}
protected void processProtoTwoWayRpcTestGateway(String deviceName) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
TransportApiProtos.ConnectMsg connectMsgProto = getConnectProto(deviceName);
byte[] payloadBytes = connectMsgProto.toByteArray();
validateProtoTwoWayRpcGatewayResponse(deviceName, client, payloadBytes);
}
protected void processProtoOneWayRpcTestGateway(String deviceName) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
TransportApiProtos.ConnectMsg connectMsgProto = getConnectProto(deviceName);
byte[] payloadBytes = connectMsgProto.toByteArray();
validateOneWayRpcGatewayResponse(deviceName, client, payloadBytes);
}
private TransportApiProtos.ConnectMsg getConnectProto(String deviceName) {
TransportApiProtos.ConnectMsg.Builder builder = TransportApiProtos.ConnectMsg.newBuilder();
builder.setDeviceName(deviceName);
builder.setDeviceType(TransportPayloadType.PROTOBUF.name());
return builder.build();
}
protected void processSequenceTwoWayRpcTest() throws Exception { protected void processSequenceTwoWayRpcTest() throws Exception {
List<String> expected = new ArrayList<>(); List<String> expected = new ArrayList<>();
List<String> result = new ArrayList<>(); List<String> result = new ArrayList<>();
@ -131,13 +194,13 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractM
Assert.assertEquals(expected, result); Assert.assertEquals(expected, result);
} }
protected void processTwoWayRpcTestGateway(String deviceName) throws Exception { protected void processJsonTwoWayRpcTestGateway(String deviceName) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken); MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
String payload = "{\"device\":\"" + deviceName + "\"}"; String payload = "{\"device\":\"" + deviceName + "\"}";
byte[] payloadBytes = payload.getBytes(); byte[] payloadBytes = payload.getBytes();
validateTwoWayRpcGateway(deviceName, client, payloadBytes); validateJsonTwoWayRpcGatewayResponse(deviceName, client, payloadBytes);
} }
protected void validateOneWayRpcGatewayResponse(String deviceName, MqttAsyncClient client, byte[] payloadBytes) throws Exception { protected void validateOneWayRpcGatewayResponse(String deviceName, MqttAsyncClient client, byte[] payloadBytes) throws Exception {
@ -151,7 +214,7 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractM
assertNotNull(savedDevice); assertNotNull(savedDevice);
CountDownLatch latch = new CountDownLatch(1); CountDownLatch latch = new CountDownLatch(1);
TestMqttCallback callback = new TestMqttCallback(client, latch); TestOneWayMqttCallback callback = new TestOneWayMqttCallback(client, latch);
client.setCallback(callback); client.setCallback(callback);
client.subscribe(MqttTopics.GATEWAY_RPC_TOPIC, MqttQoS.AT_MOST_ONCE.value()); client.subscribe(MqttTopics.GATEWAY_RPC_TOPIC, MqttQoS.AT_MOST_ONCE.value());
@ -166,7 +229,7 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractM
assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS()); assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS());
} }
protected void validateTwoWayRpcGateway(String deviceName, MqttAsyncClient client, byte[] payloadBytes) throws Exception { protected void validateJsonTwoWayRpcGatewayResponse(String deviceName, MqttAsyncClient client, byte[] payloadBytes) throws Exception {
publishMqttMsg(client, payloadBytes, MqttTopics.GATEWAY_CONNECT_TOPIC); publishMqttMsg(client, payloadBytes, MqttTopics.GATEWAY_CONNECT_TOPIC);
Device savedDevice = doExecuteWithRetriesAndInterval( Device savedDevice = doExecuteWithRetriesAndInterval(
@ -177,7 +240,34 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractM
assertNotNull(savedDevice); assertNotNull(savedDevice);
CountDownLatch latch = new CountDownLatch(1); CountDownLatch latch = new CountDownLatch(1);
TestMqttCallback callback = new TestMqttCallback(client, latch); TestJsonMqttCallback callback = new TestJsonMqttCallback(client, latch);
client.setCallback(callback);
client.subscribe(MqttTopics.GATEWAY_RPC_TOPIC, MqttQoS.AT_MOST_ONCE.value());
Thread.sleep(1000);
String setGpioRequest = "{\"method\": \"toggle_gpio\", \"params\": {\"pin\":1}}";
String deviceId = savedDevice.getId().getId().toString();
String result = doPostAsync("/api/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk());
latch.await(3, TimeUnit.SECONDS);
String expected = "{\"success\":true}";
assertEquals(expected, result);
assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS());
}
protected void validateProtoTwoWayRpcGatewayResponse(String deviceName, MqttAsyncClient client, byte[] payloadBytes) throws Exception {
publishMqttMsg(client, payloadBytes, MqttTopics.GATEWAY_CONNECT_TOPIC);
Device savedDevice = doExecuteWithRetriesAndInterval(
() -> getDeviceByName(deviceName),
20,
100
);
assertNotNull(savedDevice);
CountDownLatch latch = new CountDownLatch(1);
TestProtoMqttCallback callback = new TestProtoMqttCallback(client, latch);
client.setCallback(callback); client.setCallback(callback);
client.subscribe(MqttTopics.GATEWAY_RPC_TOPIC, MqttQoS.AT_MOST_ONCE.value()); client.subscribe(MqttTopics.GATEWAY_RPC_TOPIC, MqttQoS.AT_MOST_ONCE.value());
@ -197,7 +287,7 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractM
return doGet("/api/tenant/devices?deviceName=" + deviceName, Device.class); return doGet("/api/tenant/devices?deviceName=" + deviceName, Device.class);
} }
protected MqttMessage processMessageArrived(String requestTopic, MqttMessage mqttMessage) throws MqttException, InvalidProtocolBufferException { protected MqttMessage processJsonMessageArrived(String requestTopic, MqttMessage mqttMessage) throws MqttException, InvalidProtocolBufferException {
MqttMessage message = new MqttMessage(); MqttMessage message = new MqttMessage();
if (requestTopic.startsWith(MqttTopics.BASE_DEVICE_API_TOPIC) || requestTopic.startsWith(MqttTopics.BASE_DEVICE_API_TOPIC_V2)) { if (requestTopic.startsWith(MqttTopics.BASE_DEVICE_API_TOPIC) || requestTopic.startsWith(MqttTopics.BASE_DEVICE_API_TOPIC_V2)) {
message.setPayload(DEVICE_RESPONSE.getBytes(StandardCharset.UTF_8)); message.setPayload(DEVICE_RESPONSE.getBytes(StandardCharset.UTF_8));
@ -210,13 +300,45 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractM
return message; return message;
} }
protected class TestMqttCallback implements MqttCallback { protected class TestOneWayMqttCallback implements MqttCallback {
private final MqttAsyncClient client;
private final CountDownLatch latch;
private Integer qoS;
TestOneWayMqttCallback(MqttAsyncClient client, CountDownLatch latch) {
this.client = client;
this.latch = latch;
}
int getQoS() {
return qoS;
}
@Override
public void connectionLost(Throwable throwable) {
}
@Override
public void messageArrived(String requestTopic, MqttMessage mqttMessage) throws Exception {
log.info("Message Arrived: " + Arrays.toString(mqttMessage.getPayload()));
qoS = mqttMessage.getQos();
latch.countDown();
}
@Override
public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
}
}
protected class TestJsonMqttCallback implements MqttCallback {
private final MqttAsyncClient client; private final MqttAsyncClient client;
private final CountDownLatch latch; private final CountDownLatch latch;
private Integer qoS; private Integer qoS;
TestMqttCallback(MqttAsyncClient client, CountDownLatch latch) { TestJsonMqttCallback(MqttAsyncClient client, CountDownLatch latch) {
this.client = client; this.client = client;
this.latch = latch; this.latch = latch;
} }
@ -239,7 +361,7 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractM
responseTopic = requestTopic.replace("request", "response"); responseTopic = requestTopic.replace("request", "response");
} }
qoS = mqttMessage.getQos(); qoS = mqttMessage.getQos();
client.publish(responseTopic, processMessageArrived(requestTopic, mqttMessage)); client.publish(responseTopic, processJsonMessageArrived(requestTopic, mqttMessage));
latch.countDown(); latch.countDown();
} }
@ -249,6 +371,98 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractM
} }
} }
protected class TestProtoMqttCallback implements MqttCallback {
private final MqttAsyncClient client;
private final CountDownLatch latch;
private Integer qoS;
TestProtoMqttCallback(MqttAsyncClient client, CountDownLatch latch) {
this.client = client;
this.latch = latch;
}
int getQoS() {
return qoS;
}
@Override
public void connectionLost(Throwable throwable) {
}
@Override
public void messageArrived(String requestTopic, MqttMessage mqttMessage) throws Exception {
log.info("Message Arrived: " + Arrays.toString(mqttMessage.getPayload()));
String responseTopic;
if (requestTopic.startsWith(MqttTopics.BASE_DEVICE_API_TOPIC_V2)) {
responseTopic = requestTopic.replace("req", "res");
} else {
responseTopic = requestTopic.replace("request", "response");
}
qoS = mqttMessage.getQos();
client.publish(responseTopic, processProtoMessageArrived(requestTopic, mqttMessage));
latch.countDown();
}
@Override
public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
}
}
protected MqttMessage processProtoMessageArrived(String requestTopic, MqttMessage mqttMessage) throws MqttException, InvalidProtocolBufferException {
MqttMessage message = new MqttMessage();
if (requestTopic.startsWith(MqttTopics.BASE_DEVICE_API_TOPIC) || requestTopic.startsWith(MqttTopics.BASE_DEVICE_API_TOPIC_V2)) {
ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = getProtoTransportPayloadConfiguration();
ProtoFileElement rpcRequestProtoSchemaFile = protoTransportPayloadConfiguration.getTransportProtoSchema(RPC_REQUEST_PROTO_SCHEMA);
DynamicSchema rpcRequestProtoSchema = protoTransportPayloadConfiguration.getDynamicSchema(rpcRequestProtoSchemaFile, ProtoTransportPayloadConfiguration.RPC_REQUEST_PROTO_SCHEMA);
byte[] requestPayload = mqttMessage.getPayload();
DynamicMessage.Builder rpcRequestMsg = rpcRequestProtoSchema.newMessageBuilder("RpcRequestMsg");
Descriptors.Descriptor rpcRequestMsgDescriptor = rpcRequestMsg.getDescriptorForType();
assertNotNull(rpcRequestMsgDescriptor);
try {
DynamicMessage dynamicMessage = DynamicMessage.parseFrom(rpcRequestMsgDescriptor, requestPayload);
List<Descriptors.FieldDescriptor> fields = rpcRequestMsgDescriptor.getFields();
for (Descriptors.FieldDescriptor fieldDescriptor: fields) {
assertTrue(dynamicMessage.hasField(fieldDescriptor));
}
ProtoFileElement transportProtoSchemaFile = protoTransportPayloadConfiguration.getTransportProtoSchema(DEVICE_RPC_RESPONSE_PROTO_SCHEMA);
DynamicSchema rpcResponseProtoSchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchemaFile, ProtoTransportPayloadConfiguration.RPC_RESPONSE_PROTO_SCHEMA);
DynamicMessage.Builder rpcResponseBuilder = rpcResponseProtoSchema.newMessageBuilder("RpcResponseMsg");
Descriptors.Descriptor rpcResponseMsgDescriptor = rpcResponseBuilder.getDescriptorForType();
assertNotNull(rpcResponseMsgDescriptor);
DynamicMessage rpcResponseMsg = rpcResponseBuilder
.setField(rpcResponseMsgDescriptor.findFieldByName("payload"), DEVICE_RESPONSE)
.build();
message.setPayload(rpcResponseMsg.toByteArray());
} catch (InvalidProtocolBufferException e) {
log.warn("Command Response Ack Error, Invalid response received: ", e);
}
} else {
TransportApiProtos.GatewayDeviceRpcRequestMsg msg = TransportApiProtos.GatewayDeviceRpcRequestMsg.parseFrom(mqttMessage.getPayload());
String deviceName = msg.getDeviceName();
int requestId = msg.getRpcRequestMsg().getRequestId();
TransportApiProtos.GatewayRpcResponseMsg gatewayRpcResponseMsg = TransportApiProtos.GatewayRpcResponseMsg.newBuilder()
.setDeviceName(deviceName)
.setId(requestId)
.setData("{\"success\": true}")
.build();
message.setPayload(gatewayRpcResponseMsg.toByteArray());
}
return message;
}
private ProtoTransportPayloadConfiguration getProtoTransportPayloadConfiguration() {
DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration();
assertTrue(transportConfiguration instanceof MqttDeviceProfileTransportConfiguration);
MqttDeviceProfileTransportConfiguration mqttTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration;
TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = mqttTransportConfiguration.getTransportPayloadTypeConfiguration();
assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration);
return (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration;
}
protected class TestSequenceMqttCallback implements MqttCallback { protected class TestSequenceMqttCallback implements MqttCallback {
private final MqttAsyncClient client; private final MqttAsyncClient client;
@ -273,7 +487,7 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractM
var qoS = mqttMessage.getQos(); var qoS = mqttMessage.getQos();
client.messageArrivedComplete(mqttMessage.getId(), qoS); client.messageArrivedComplete(mqttMessage.getId(), qoS);
client.publish(responseTopic, processMessageArrived(requestTopic, mqttMessage)); client.publish(responseTopic, processJsonMessageArrived(requestTopic, mqttMessage));
latch.countDown(); latch.countDown();
} }

12
application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcJsonIntegrationTest.java

@ -53,30 +53,30 @@ public abstract class AbstractMqttServerSideRpcJsonIntegrationTest extends Abstr
@Test @Test
public void testServerMqttTwoWayRpc() throws Exception { public void testServerMqttTwoWayRpc() throws Exception {
processTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC); processJsonTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC);
} }
@Test @Test
public void testServerMqttTwoWayRpcOnShortTopic() throws Exception { public void testServerMqttTwoWayRpcOnShortTopic() throws Exception {
processTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_TOPIC); processJsonTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_TOPIC);
} }
@Test @Test
public void testServerMqttTwoWayRpcOnShortJsonTopic() throws Exception { public void testServerMqttTwoWayRpcOnShortJsonTopic() throws Exception {
processTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_JSON_TOPIC); processJsonTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_JSON_TOPIC);
} }
@Test @Test
public void testGatewayServerMqttOneWayRpc() throws Exception { public void testGatewayServerMqttOneWayRpc() throws Exception {
processOneWayRpcTestGateway("Gateway Device OneWay RPC Json"); processJsonOneWayRpcTestGateway("Gateway Device OneWay RPC Json");
} }
@Test @Test
public void testGatewayServerMqttTwoWayRpc() throws Exception { public void testGatewayServerMqttTwoWayRpc() throws Exception {
processTwoWayRpcTestGateway("Gateway Device TwoWay RPC Json"); processJsonTwoWayRpcTestGateway("Gateway Device TwoWay RPC Json");
} }
protected void processOneWayRpcTestGateway(String deviceName) throws Exception { protected void processJsonOneWayRpcTestGateway(String deviceName) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken); MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
String payload = "{\"device\": \"" + deviceName + "\", \"type\": \"" + TransportPayloadType.JSON.name() + "\"}"; String payload = "{\"device\": \"" + deviceName + "\", \"type\": \"" + TransportPayloadType.JSON.name() + "\"}";
byte[] payloadBytes = payload.getBytes(); byte[] payloadBytes = payload.getBytes();

147
application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java

@ -15,60 +15,20 @@
*/ */
package org.thingsboard.server.transport.mqtt.rpc; package org.thingsboard.server.transport.mqtt.rpc;
import com.github.os72.protobuf.dynamic.DynamicSchema;
import com.google.protobuf.Descriptors;
import com.google.protobuf.DynamicMessage;
import com.google.protobuf.InvalidProtocolBufferException;
import com.squareup.wire.schema.internal.parser.ProtoFileElement;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.core.CoapHandler;
import org.eclipse.californium.core.CoapResponse;
import org.eclipse.californium.core.coap.MediaTypeRegistry;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.jetbrains.annotations.NotNull;
import org.junit.After; import org.junit.After;
import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.DeviceProfileProvisionType;
import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration;
import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration;
import org.thingsboard.server.gen.transport.TransportApiProtos;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Slf4j @Slf4j
public abstract class AbstractMqttServerSideRpcProtoIntegrationTest extends AbstractMqttServerSideRpcIntegrationTest { public abstract class AbstractMqttServerSideRpcProtoIntegrationTest extends AbstractMqttServerSideRpcIntegrationTest {
private static final String RPC_REQUEST_PROTO_SCHEMA = "syntax =\"proto3\";\n" +
"package rpc;\n" +
"\n" +
"message RpcRequestMsg {\n" +
" optional string method = 1;\n" +
" optional int32 requestId = 2;\n" +
" Params params = 3;\n" +
"\n" +
" message Params {\n" +
" optional string pin = 1;\n" +
" optional int32 value = 2;\n" +
" }\n" +
"}";
@Before @Before
public void beforeTest() throws Exception { public void beforeTest() throws Exception {
processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED); processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED, false, false);
} }
@After @After
@ -93,122 +53,27 @@ public abstract class AbstractMqttServerSideRpcProtoIntegrationTest extends Abst
@Test @Test
public void testServerMqttTwoWayRpc() throws Exception { public void testServerMqttTwoWayRpc() throws Exception {
processTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC); processProtoTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC);
} }
@Test @Test
public void testServerMqttTwoWayRpcOnShortTopic() throws Exception { public void testServerMqttTwoWayRpcOnShortTopic() throws Exception {
processTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_TOPIC); processProtoTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_TOPIC);
} }
@Test @Test
public void testServerMqttTwoWayRpcOnShortProtoTopic() throws Exception { public void testServerMqttTwoWayRpcOnShortProtoTopic() throws Exception {
processTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_PROTO_TOPIC); processProtoTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_PROTO_TOPIC);
} }
@Test @Test
public void testGatewayServerMqttOneWayRpc() throws Exception { public void testGatewayServerMqttOneWayRpc() throws Exception {
processOneWayRpcTestGateway("Gateway Device OneWay RPC Proto"); processProtoOneWayRpcTestGateway("Gateway Device OneWay RPC Proto");
} }
@Test @Test
public void testGatewayServerMqttTwoWayRpc() throws Exception { public void testGatewayServerMqttTwoWayRpc() throws Exception {
processTwoWayRpcTestGateway("Gateway Device TwoWay RPC Proto"); processProtoTwoWayRpcTestGateway("Gateway Device TwoWay RPC Proto");
}
protected void processTwoWayRpcTestGateway(String deviceName) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
TransportApiProtos.ConnectMsg connectMsgProto = getConnectProto(deviceName);
byte[] payloadBytes = connectMsgProto.toByteArray();
validateTwoWayRpcGateway(deviceName, client, payloadBytes);
}
protected void processOneWayRpcTestGateway(String deviceName) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
TransportApiProtos.ConnectMsg connectMsgProto = getConnectProto(deviceName);
byte[] payloadBytes = connectMsgProto.toByteArray();
validateOneWayRpcGatewayResponse(deviceName, client, payloadBytes);
}
private TransportApiProtos.ConnectMsg getConnectProto(String deviceName) {
TransportApiProtos.ConnectMsg.Builder builder = TransportApiProtos.ConnectMsg.newBuilder();
builder.setDeviceName(deviceName);
builder.setDeviceType(TransportPayloadType.PROTOBUF.name());
return builder.build();
} }
protected void processTwoWayRpcTest(String rpcSubTopic) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(accessToken);
client.subscribe(rpcSubTopic, 1);
CountDownLatch latch = new CountDownLatch(1);
TestMqttCallback callback = new TestMqttCallback(client, latch);
client.setCallback(callback);
Thread.sleep(1000);
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"26\",\"value\": 1}}";
String deviceId = savedDevice.getId().getId().toString();
String result = doPostAsync("/api/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk());
String expected = "{\"payload\":\"{\\\"value1\\\":\\\"A\\\",\\\"value2\\\":\\\"B\\\"}\"}";
latch.await(3, TimeUnit.SECONDS);
Assert.assertEquals(expected, result);
}
protected MqttMessage processMessageArrived(String requestTopic, MqttMessage mqttMessage) throws MqttException, InvalidProtocolBufferException {
MqttMessage message = new MqttMessage();
if (requestTopic.startsWith(MqttTopics.BASE_DEVICE_API_TOPIC) || requestTopic.startsWith(MqttTopics.BASE_DEVICE_API_TOPIC_V2)) {
ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = getProtoTransportPayloadConfiguration();
ProtoFileElement rpcRequestProtoSchemaFile = protoTransportPayloadConfiguration.getTransportProtoSchema(RPC_REQUEST_PROTO_SCHEMA);
DynamicSchema rpcRequestProtoSchema = protoTransportPayloadConfiguration.getDynamicSchema(rpcRequestProtoSchemaFile, ProtoTransportPayloadConfiguration.RPC_REQUEST_PROTO_SCHEMA);
byte[] requestPayload = mqttMessage.getPayload();
DynamicMessage.Builder rpcRequestMsg = rpcRequestProtoSchema.newMessageBuilder("RpcRequestMsg");
Descriptors.Descriptor rpcRequestMsgDescriptor = rpcRequestMsg.getDescriptorForType();
assertNotNull(rpcRequestMsgDescriptor);
try {
DynamicMessage dynamicMessage = DynamicMessage.parseFrom(rpcRequestMsgDescriptor, requestPayload);
List<Descriptors.FieldDescriptor> fields = rpcRequestMsgDescriptor.getFields();
for (Descriptors.FieldDescriptor fieldDescriptor: fields) {
assertTrue(dynamicMessage.hasField(fieldDescriptor));
}
ProtoFileElement transportProtoSchemaFile = protoTransportPayloadConfiguration.getTransportProtoSchema(DEVICE_RPC_RESPONSE_PROTO_SCHEMA);
DynamicSchema rpcResponseProtoSchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchemaFile, ProtoTransportPayloadConfiguration.RPC_RESPONSE_PROTO_SCHEMA);
DynamicMessage.Builder rpcResponseBuilder = rpcResponseProtoSchema.newMessageBuilder("RpcResponseMsg");
Descriptors.Descriptor rpcResponseMsgDescriptor = rpcResponseBuilder.getDescriptorForType();
assertNotNull(rpcResponseMsgDescriptor);
DynamicMessage rpcResponseMsg = rpcResponseBuilder
.setField(rpcResponseMsgDescriptor.findFieldByName("payload"), DEVICE_RESPONSE)
.build();
message.setPayload(rpcResponseMsg.toByteArray());
} catch (InvalidProtocolBufferException e) {
log.warn("Command Response Ack Error, Invalid response received: ", e);
}
} else {
TransportApiProtos.GatewayDeviceRpcRequestMsg msg = TransportApiProtos.GatewayDeviceRpcRequestMsg.parseFrom(mqttMessage.getPayload());
String deviceName = msg.getDeviceName();
int requestId = msg.getRpcRequestMsg().getRequestId();
TransportApiProtos.GatewayRpcResponseMsg gatewayRpcResponseMsg = TransportApiProtos.GatewayRpcResponseMsg.newBuilder()
.setDeviceName(deviceName)
.setId(requestId)
.setData("{\"success\": true}")
.build();
message.setPayload(gatewayRpcResponseMsg.toByteArray());
}
return message;
}
private ProtoTransportPayloadConfiguration getProtoTransportPayloadConfiguration() {
DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration();
assertTrue(transportConfiguration instanceof MqttDeviceProfileTransportConfiguration);
MqttDeviceProfileTransportConfiguration mqttTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration;
TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = mqttTransportConfiguration.getTransportPayloadTypeConfiguration();
assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration);
return (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration;
}
} }

24
application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/sql/MqttServerSideRpcBackwardCompatibilityIntegrationTest.java

@ -0,0 +1,24 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.mqtt.rpc.sql;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.transport.mqtt.rpc.AbstractMqttServerSideRpcBackwardCompatibilityIntegrationTest;
@DaoSqlTest
public class MqttServerSideRpcBackwardCompatibilityIntegrationTest extends AbstractMqttServerSideRpcBackwardCompatibilityIntegrationTest {
}

2
application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/sql/MqttServerSideRpcSqlIntegrationTest.java → application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/sql/MqttServerSideRpcIntegrationTest.java

@ -22,5 +22,5 @@ import org.thingsboard.server.transport.mqtt.rpc.AbstractMqttServerSideRpcDefaul
* Created by Valerii Sosliuk on 8/22/2017. * Created by Valerii Sosliuk on 8/22/2017.
*/ */
@DaoSqlTest @DaoSqlTest
public class MqttServerSideRpcSqlIntegrationTest extends AbstractMqttServerSideRpcDefaultIntegrationTest { public class MqttServerSideRpcIntegrationTest extends AbstractMqttServerSideRpcDefaultIntegrationTest {
} }

2
application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/sql/MqttServerSideRpcJsonSqlIntegrationTest.java → application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/sql/MqttServerSideRpcJsonIntegrationTest.java

@ -19,5 +19,5 @@ import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.transport.mqtt.rpc.AbstractMqttServerSideRpcJsonIntegrationTest; import org.thingsboard.server.transport.mqtt.rpc.AbstractMqttServerSideRpcJsonIntegrationTest;
@DaoSqlTest @DaoSqlTest
public class MqttServerSideRpcJsonSqlIntegrationTest extends AbstractMqttServerSideRpcJsonIntegrationTest { public class MqttServerSideRpcJsonIntegrationTest extends AbstractMqttServerSideRpcJsonIntegrationTest {
} }

2
application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/sql/MqttServerSideRpcProtoSqlIntegrationTest.java → application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/sql/MqttServerSideRpcProtoIntegrationTest.java

@ -20,5 +20,5 @@ import org.thingsboard.server.transport.mqtt.rpc.AbstractMqttServerSideRpcProtoI
@DaoSqlTest @DaoSqlTest
public class MqttServerSideRpcProtoSqlIntegrationTest extends AbstractMqttServerSideRpcProtoIntegrationTest { public class MqttServerSideRpcProtoIntegrationTest extends AbstractMqttServerSideRpcProtoIntegrationTest {
} }

6
application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java

@ -54,6 +54,12 @@ public abstract class AbstractMqttAttributesProtoIntegrationTest extends Abstrac
processAttributesTest(POST_DATA_ATTRIBUTES_TOPIC, Arrays.asList("key1", "key2", "key3", "key4", "key5"), postAttributesMsg.toByteArray(), false); processAttributesTest(POST_DATA_ATTRIBUTES_TOPIC, Arrays.asList("key1", "key2", "key3", "key4", "key5"), postAttributesMsg.toByteArray(), false);
} }
@Test
public void testPushAttributesWithEnabledJsonBackwardCompatibility() throws Exception {
super.processBeforeTest("Test Post Attributes device", "Test Post Attributes gateway", TransportPayloadType.PROTOBUF, null, POST_DATA_ATTRIBUTES_TOPIC, true, false);
processJsonPayloadAttributesTest(POST_DATA_ATTRIBUTES_TOPIC, Arrays.asList("key1", "key2", "key3", "key4", "key5"), PAYLOAD_VALUES_STR.getBytes());
}
@Test @Test
public void testPushAttributesWithExplicitPresenceProtoKeys() throws Exception { public void testPushAttributesWithExplicitPresenceProtoKeys() throws Exception {
super.processBeforeTest("Test Post Attributes device", "Test Post Attributes gateway", TransportPayloadType.PROTOBUF, null, POST_DATA_ATTRIBUTES_TOPIC); super.processBeforeTest("Test Post Attributes device", "Test Post Attributes gateway", TransportPayloadType.PROTOBUF, null, POST_DATA_ATTRIBUTES_TOPIC);

2
application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/sql/MqttAttributesSqlIntegrationTest.java → application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/sql/MqttAttributesIntegrationTest.java

@ -19,5 +19,5 @@ import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.transport.mqtt.telemetry.attributes.AbstractMqttAttributesIntegrationTest; import org.thingsboard.server.transport.mqtt.telemetry.attributes.AbstractMqttAttributesIntegrationTest;
@DaoSqlTest @DaoSqlTest
public class MqttAttributesSqlIntegrationTest extends AbstractMqttAttributesIntegrationTest { public class MqttAttributesIntegrationTest extends AbstractMqttAttributesIntegrationTest {
} }

2
application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/sql/MqttAttributesSqlJsonIntegrationTest.java → application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/sql/MqttAttributesJsonIntegrationTest.java

@ -19,5 +19,5 @@ import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.transport.mqtt.telemetry.attributes.AbstractMqttAttributesJsonIntegrationTest; import org.thingsboard.server.transport.mqtt.telemetry.attributes.AbstractMqttAttributesJsonIntegrationTest;
@DaoSqlTest @DaoSqlTest
public class MqttAttributesSqlJsonIntegrationTest extends AbstractMqttAttributesJsonIntegrationTest { public class MqttAttributesJsonIntegrationTest extends AbstractMqttAttributesJsonIntegrationTest {
} }

2
application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/sql/MqttAttributesSqlProtoIntegrationTest.java → application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/sql/MqttAttributesProtoIntegrationTest.java

@ -19,5 +19,5 @@ import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.transport.mqtt.telemetry.attributes.AbstractMqttAttributesProtoIntegrationTest; import org.thingsboard.server.transport.mqtt.telemetry.attributes.AbstractMqttAttributesProtoIntegrationTest;
@DaoSqlTest @DaoSqlTest
public class MqttAttributesSqlProtoIntegrationTest extends AbstractMqttAttributesProtoIntegrationTest { public class MqttAttributesProtoIntegrationTest extends AbstractMqttAttributesProtoIntegrationTest {
} }

17
application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java

@ -21,9 +21,7 @@ import com.google.protobuf.DynamicMessage;
import com.squareup.wire.schema.internal.parser.ProtoFileElement; import com.squareup.wire.schema.internal.parser.ProtoFileElement;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient; import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.jetbrains.annotations.NotNull;
import org.junit.After; import org.junit.After;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.DeviceProfileProvisionType;
@ -37,7 +35,6 @@ import org.thingsboard.server.gen.transport.TransportApiProtos;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
@ -60,6 +57,12 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac
processTelemetryTest(POST_DATA_TELEMETRY_TOPIC, Arrays.asList("key1", "key2", "key3", "key4", "key5"), postTelemetryMsg.toByteArray(), false, false); processTelemetryTest(POST_DATA_TELEMETRY_TOPIC, Arrays.asList("key1", "key2", "key3", "key4", "key5"), postTelemetryMsg.toByteArray(), false, false);
} }
@Test
public void testPushTelemetryWithEnabledJsonBackwardCompatibility() throws Exception {
super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, true, false);
processJsonPayloadTelemetryTest(POST_DATA_TELEMETRY_TOPIC, Arrays.asList("key1", "key2", "key3", "key4", "key5"), PAYLOAD_VALUES_STR.getBytes(), false);
}
@Test @Test
public void testPushTelemetryWithTs() throws Exception { public void testPushTelemetryWithTs() throws Exception {
String schemaStr = "syntax =\"proto3\";\n" + String schemaStr = "syntax =\"proto3\";\n" +
@ -87,7 +90,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac
" }\n" + " }\n" +
" }\n" + " }\n" +
"}"; "}";
super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, schemaStr, null, null, null, null, null, DeviceProfileProvisionType.DISABLED); super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, schemaStr, null, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false);
DynamicSchema telemetrySchema = getDynamicSchema(schemaStr); DynamicSchema telemetrySchema = getDynamicSchema(schemaStr);
DynamicMessage.Builder nestedJsonObjectBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.JsonObject.NestedJsonObject"); DynamicMessage.Builder nestedJsonObjectBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.JsonObject.NestedJsonObject");
@ -189,7 +192,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac
" }\n" + " }\n" +
" }\n" + " }\n" +
"}"; "}";
super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, schemaStr, null, null, null, null, null, DeviceProfileProvisionType.DISABLED); super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, schemaStr, null, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false);
DynamicSchema telemetrySchema = getDynamicSchema(schemaStr); DynamicSchema telemetrySchema = getDynamicSchema(schemaStr);
DynamicMessage.Builder nestedJsonObjectBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.JsonObject.NestedJsonObject"); DynamicMessage.Builder nestedJsonObjectBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.JsonObject.NestedJsonObject");
@ -249,7 +252,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac
@Test @Test
public void testPushTelemetryGateway() throws Exception { public void testPushTelemetryGateway() throws Exception {
super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED); super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false);
TransportApiProtos.GatewayTelemetryMsg.Builder gatewayTelemetryMsgProtoBuilder = TransportApiProtos.GatewayTelemetryMsg.newBuilder(); TransportApiProtos.GatewayTelemetryMsg.Builder gatewayTelemetryMsgProtoBuilder = TransportApiProtos.GatewayTelemetryMsg.newBuilder();
List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
String deviceName1 = "Device A"; String deviceName1 = "Device A";
@ -263,7 +266,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac
@Test @Test
public void testGatewayConnect() throws Exception { public void testGatewayConnect() throws Exception {
super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED); super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false);
String deviceName = "Device A"; String deviceName = "Device A";
TransportApiProtos.ConnectMsg connectMsgProto = getConnectProto(deviceName); TransportApiProtos.ConnectMsg connectMsgProto = getConnectProto(deviceName);
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken); MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);

25
application/src/test/resources/application-test.properties

@ -1,10 +1,21 @@
transport.lwm2m.security.key_store=lwm2m/credentials/serverKeyStore.jks transport.lwm2m.server.security.credentials.enabled=true
transport.lwm2m.security.key_store_password=server transport.lwm2m.server.security.credentials.type=KEYSTORE
transport.lwm2m.server.security.credentials.keystore.store_file=lwm2m/credentials/serverKeyStore.jks
transport.lwm2m.server.security.credentials.keystore.store_password=server
transport.lwm2m.server.security.credentials.keystore.key_alias=server
transport.lwm2m.server.security.credentials.keystore.key_password=server
transport.lwm2m.bootstrap.security.credentials.enabled=true
transport.lwm2m.bootstrap.security.credentials.type=KEYSTORE
transport.lwm2m.bootstrap.security.credentials.keystore.store_file=lwm2m/credentials/serverKeyStore.jks
transport.lwm2m.bootstrap.security.credentials.keystore.store_password=server
transport.lwm2m.bootstrap.security.credentials.keystore.key_alias=server
transport.lwm2m.bootstrap.security.credentials.keystore.key_password=server
transport.lwm2m.security.trust-credentials.enabled=true
transport.lwm2m.security.trust-credentials.type=KEYSTORE
transport.lwm2m.security.trust-credentials.keystore.store_file=lwm2m/credentials/serverKeyStore.jks
transport.lwm2m.security.trust-credentials.keystore.store_password=server
edges.enabled=true edges.enabled=true
edges.storage.no_read_records_sleep=500 edges.storage.no_read_records_sleep=500
edges.storage.sleep_between_batches=500 edges.storage.sleep_between_batches=500
transport.lwm2m.server.security.key_alias=server actors.rpc.sequential=true
transport.lwm2m.server.security.key_password=server
transport.lwm2m.bootstrap.security.key_alias=server
transport.lwm2m.bootstrap.security.key_password=server
actors.rpc.sequential=true

3
application/src/test/resources/update/330/README.md

@ -0,0 +1,3 @@
To get json from live Thingsboard instance use those methods:
1. Browser: F12 -> Network -> open device profile page -> copy raw response
2. Database: SELECT * FROM public.device_profile WHERE name = 'LORAWAN 001';

173
application/src/test/resources/update/330/device_profile_001_in.json

@ -0,0 +1,173 @@
{
"id": {
"entityType": "DEVICE_PROFILE",
"id": "b99fde7a-33dd-4d5d-a325-d0637f6acbe5"
},
"createdTime": 1627268171906,
"tenantId": {
"entityType": "TENANT",
"id": "3db30ac6-db03-4788-98fe-6e024b422a15"
},
"name": "LORAWAN 001",
"description": "Tektelic - 001",
"type": "DEFAULT",
"transportType": "DEFAULT",
"provisionType": "DISABLED",
"defaultRuleChainId": {
"entityType": "RULE_CHAIN",
"id": "9c50f4df-f41e-443f-bb7d-37b5ac97f3c3"
},
"defaultQueueName": "LORAWAN",
"profileData": {
"configuration": {
"type": "DEFAULT"
},
"transportConfiguration": {
"type": "DEFAULT"
},
"provisionConfiguration": {
"type": "DISABLED",
"provisionDeviceSecret": null
},
"alarms": [
{
"id": "b86271fd-5fee-4bd5-975c-d9c18f610cd5",
"alarmType": "LORAWAN - Battery Alarm",
"createRules": {
"CRITICAL": {
"condition": {
"condition": [
{
"key": {
"type": "TIME_SERIES",
"key": "batteryLevel"
},
"valueType": "NUMERIC",
"value": null,
"predicate": {
"type": "NUMERIC",
"operation": "LESS",
"value": {
"defaultValue": 25.0,
"userValue": null,
"dynamicValue": null
}
}
}
],
"spec": {
"type": "DURATION",
"unit": "DAYS",
"value": 1
}
},
"schedule": null,
"alarmDetails": null
}
},
"clearRule": {
"condition": {
"condition": [
{
"key": {
"type": "TIME_SERIES",
"key": "batteryLevel"
},
"valueType": "NUMERIC",
"value": null,
"predicate": {
"type": "NUMERIC",
"operation": "GREATER_OR_EQUAL",
"value": {
"defaultValue": 25.0,
"userValue": null,
"dynamicValue": null
}
}
}
],
"spec": {
"type": "DURATION",
"unit": "DAYS",
"value": 1
}
},
"schedule": null,
"alarmDetails": null
},
"propagate": true,
"propagateRelationTypes": [
"UC-0007 LORAWAN"
]
},
{
"id": "c70aef4e-65cf-4578-acd9-e1927c08b469",
"alarmType": "LORAWAN - No Data",
"createRules": {
"CRITICAL": {
"condition": {
"condition": [
{
"key": {
"type": "TIME_SERIES",
"key": "active"
},
"valueType": "BOOLEAN",
"value": null,
"predicate": {
"type": "BOOLEAN",
"operation": "EQUAL",
"value": {
"defaultValue": false,
"userValue": null,
"dynamicValue": null
}
}
}
],
"spec": {
"type": "SIMPLE"
}
},
"schedule": null,
"alarmDetails": null
}
},
"clearRule": {
"condition": {
"condition": [
{
"key": {
"type": "TIME_SERIES",
"key": "active"
},
"valueType": "BOOLEAN",
"value": null,
"predicate": {
"type": "BOOLEAN",
"operation": "EQUAL",
"value": {
"defaultValue": true,
"userValue": null,
"dynamicValue": null
}
}
}
],
"spec": {
"type": "SIMPLE"
}
},
"schedule": null,
"alarmDetails": null
},
"propagate": true,
"propagateRelationTypes": [
"LORAWAN 001 related"
]
}
]
},
"provisionDeviceKey": null,
"default": false
}

189
application/src/test/resources/update/330/device_profile_001_out.json

@ -0,0 +1,189 @@
{
"id": {
"entityType": "DEVICE_PROFILE",
"id": "b99fde7a-33dd-4d5d-a325-d0637f6acbe5"
},
"createdTime": 1627268171906,
"tenantId": {
"entityType": "TENANT",
"id": "3db30ac6-db03-4788-98fe-6e024b422a15"
},
"name": "LORAWAN 001",
"description": "Tektelic - 001",
"type": "DEFAULT",
"transportType": "DEFAULT",
"provisionType": "DISABLED",
"defaultRuleChainId": {
"entityType": "RULE_CHAIN",
"id": "9c50f4df-f41e-443f-bb7d-37b5ac97f3c3"
},
"defaultQueueName": "LORAWAN",
"profileData": {
"configuration": {
"type": "DEFAULT"
},
"transportConfiguration": {
"type": "DEFAULT"
},
"provisionConfiguration": {
"type": "DISABLED",
"provisionDeviceSecret": null
},
"alarms": [
{
"id": "b86271fd-5fee-4bd5-975c-d9c18f610cd5",
"alarmType": "LORAWAN - Battery Alarm",
"createRules": {
"CRITICAL": {
"condition": {
"condition": [
{
"key": {
"type": "TIME_SERIES",
"key": "batteryLevel"
},
"valueType": "NUMERIC",
"value": null,
"predicate": {
"type": "NUMERIC",
"operation": "LESS",
"value": {
"defaultValue": 25.0,
"userValue": null,
"dynamicValue": null
}
}
}
],
"spec": {
"type": "DURATION",
"unit": "DAYS",
"predicate": {
"defaultValue": 1,
"userValue": null,
"dynamicValue": {
"sourceType": null,
"sourceAttribute": null,
"inherit": false
}
}
}
},
"schedule": null,
"alarmDetails": null
}
},
"clearRule": {
"condition": {
"condition": [
{
"key": {
"type": "TIME_SERIES",
"key": "batteryLevel"
},
"valueType": "NUMERIC",
"value": null,
"predicate": {
"type": "NUMERIC",
"operation": "GREATER_OR_EQUAL",
"value": {
"defaultValue": 25.0,
"userValue": null,
"dynamicValue": null
}
}
}
],
"spec": {
"type": "DURATION",
"unit": "DAYS",
"predicate": {
"defaultValue": 1,
"userValue": null,
"dynamicValue": {
"sourceType": null,
"sourceAttribute": null,
"inherit": false
}
}
}
},
"schedule": null,
"alarmDetails": null
},
"propagate": true,
"propagateRelationTypes": [
"UC-0007 LORAWAN"
]
},
{
"id": "c70aef4e-65cf-4578-acd9-e1927c08b469",
"alarmType": "LORAWAN - No Data",
"createRules": {
"CRITICAL": {
"condition": {
"condition": [
{
"key": {
"type": "TIME_SERIES",
"key": "active"
},
"valueType": "BOOLEAN",
"value": null,
"predicate": {
"type": "BOOLEAN",
"operation": "EQUAL",
"value": {
"defaultValue": false,
"userValue": null,
"dynamicValue": null
}
}
}
],
"spec": {
"type": "SIMPLE"
}
},
"schedule": null,
"alarmDetails": null
}
},
"clearRule": {
"condition": {
"condition": [
{
"key": {
"type": "TIME_SERIES",
"key": "active"
},
"valueType": "BOOLEAN",
"value": null,
"predicate": {
"type": "BOOLEAN",
"operation": "EQUAL",
"value": {
"defaultValue": true,
"userValue": null,
"dynamicValue": null
}
}
}
],
"spec": {
"type": "SIMPLE"
}
},
"schedule": null,
"alarmDetails": null
},
"propagate": true,
"propagateRelationTypes": [
"LORAWAN 001 related"
]
}
]
},
"provisionDeviceKey": null,
"default": false
}

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

Loading…
Cancel
Save