Browse Source

Merge branch 'master' into fields-validation

# Conflicts:
#	common/data/src/main/java/org/thingsboard/server/common/data/OtaPackageInfo.java
#	ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.ts
#	ui-ngx/src/assets/locale/locale.constant-en_US.json
pull/5166/head
Viacheslav Klimov 5 years ago
parent
commit
f15f0e5483
  1. 16
      application/pom.xml
  2. 36
      application/src/main/data/json/system/widget_bundles/cards.json
  3. 16
      application/src/main/data/json/system/widget_bundles/control_widgets.json
  4. 5
      application/src/main/data/json/system/widget_bundles/edge_widgets.json
  5. 2
      application/src/main/data/json/system/widget_bundles/input_widgets.json
  6. 10
      application/src/main/data/json/system/widget_bundles/maps.json
  7. 7
      application/src/main/data/upgrade/3.2.2/schema_update.sql
  8. 14
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  9. 5
      application/src/main/java/org/thingsboard/server/actors/app/AppActor.java
  10. 4
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java
  11. 222
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
  12. 2
      application/src/main/java/org/thingsboard/server/actors/device/ToDeviceRpcRequestMetadata.java
  13. 10
      application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
  14. 2
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java
  15. 1
      application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
  16. 126
      application/src/main/java/org/thingsboard/server/controller/AbstractRpcController.java
  17. 8
      application/src/main/java/org/thingsboard/server/controller/AdminController.java
  18. 8
      application/src/main/java/org/thingsboard/server/controller/AlarmController.java
  19. 59
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  20. 2
      application/src/main/java/org/thingsboard/server/controller/CustomerController.java
  21. 14
      application/src/main/java/org/thingsboard/server/controller/DashboardController.java
  22. 29
      application/src/main/java/org/thingsboard/server/controller/DeviceController.java
  23. 4
      application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java
  24. 62
      application/src/main/java/org/thingsboard/server/controller/EdgeController.java
  25. 15
      application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java
  26. 1
      application/src/main/java/org/thingsboard/server/controller/OtaPackageController.java
  27. 29
      application/src/main/java/org/thingsboard/server/controller/QueueController.java
  28. 55
      application/src/main/java/org/thingsboard/server/controller/RpcV1Controller.java
  29. 128
      application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java
  30. 16
      application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
  31. 3
      application/src/main/java/org/thingsboard/server/controller/TbUrlConstants.java
  32. 4
      application/src/main/java/org/thingsboard/server/controller/TenantController.java
  33. 3
      application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java
  34. 3
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  35. 2
      application/src/main/java/org/thingsboard/server/service/action/RuleEngineEntityActionService.java
  36. 2
      application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java
  37. 57
      application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java
  38. 7
      application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java
  39. 110
      application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeLicenseService.java
  40. 26
      application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java
  41. 4
      application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java
  42. 26
      application/src/main/java/org/thingsboard/server/service/edge/EdgeLicenseService.java
  43. 4
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java
  44. 2
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java
  45. 2
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AlarmMsgConstructor.java
  46. 2
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AssetMsgConstructor.java
  47. 2
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/CustomerMsgConstructor.java
  48. 2
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DashboardMsgConstructor.java
  49. 5
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceProfileMsgConstructor.java
  50. 2
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EntityViewMsgConstructor.java
  51. 8
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/RelationMsgConstructor.java
  52. 2
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/RuleChainMsgConstructor.java
  53. 5
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/UserMsgConstructor.java
  54. 32
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetTypeMsgConstructor.java
  55. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetsBundleMsgConstructor.java
  56. 68
      application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java
  57. 109
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/AlarmEdgeProcessor.java
  58. 33
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java
  59. 56
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceEdgeProcessor.java
  60. 4
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/RelationEdgeProcessor.java
  61. 161
      application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java
  62. 89
      application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
  63. 30
      application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java
  64. 127
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java
  65. 4
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
  66. 4
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java
  67. 18
      application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbCoreDeviceRpcService.java
  68. 7
      application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbRuleEngineRpcService.java
  69. 3
      application/src/main/java/org/thingsboard/server/service/rpc/FromDeviceRpcResponseActorMsg.java
  70. 44
      application/src/main/java/org/thingsboard/server/service/rpc/RemoveRpcActorMsg.java
  71. 3
      application/src/main/java/org/thingsboard/server/service/rpc/TbCoreDeviceRpcService.java
  72. 4
      application/src/main/java/org/thingsboard/server/service/rpc/TbRpcService.java
  73. 1
      application/src/main/java/org/thingsboard/server/service/rpc/TbRuleEngineDeviceRpcService.java
  74. 2
      application/src/main/java/org/thingsboard/server/service/rpc/ToDeviceRpcRequestActorMsg.java
  75. 4
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java
  76. 11
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java
  77. 4
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java
  78. 125
      application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java
  79. 15
      application/src/main/java/org/thingsboard/server/service/state/DeviceStateService.java
  80. 20
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java
  81. 3
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java
  82. 4
      application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java
  83. 2
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java
  84. 2
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java
  85. 51
      application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java
  86. 144
      application/src/main/resources/thingsboard.yml
  87. 55
      application/src/test/java/org/thingsboard/server/cache/CaffeineCacheDefaultConfigurationTestSuite.java
  88. 2
      application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java
  89. 2
      application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java
  90. 9
      application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java
  91. 35
      application/src/test/java/org/thingsboard/server/controller/BaseEdgeEventControllerTest.java
  92. 2
      application/src/test/java/org/thingsboard/server/controller/BaseWebsocketApiTest.java
  93. 109
      application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java
  94. 6
      application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java
  95. 2
      application/src/test/java/org/thingsboard/server/service/ServiceSqlTestSuite.java
  96. 8
      application/src/test/java/org/thingsboard/server/service/cluster/routing/HashPartitionServiceTest.java
  97. 3
      application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java
  98. 2
      application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java
  99. 2
      application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java
  100. 8
      application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/AbstractCoapAttributesUpdatesIntegrationTest.java

16
application/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.3.0-SNAPSHOT</version> <version>3.3.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>application</artifactId> <artifactId>application</artifactId>
@ -65,6 +65,10 @@
<groupId>org.thingsboard.rule-engine</groupId> <groupId>org.thingsboard.rule-engine</groupId>
<artifactId>rule-engine-api</artifactId> <artifactId>rule-engine-api</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.thingsboard.common</groupId>
<artifactId>cluster-api</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.thingsboard.rule-engine</groupId> <groupId>org.thingsboard.rule-engine</groupId>
<artifactId>rule-engine-components</artifactId> <artifactId>rule-engine-components</artifactId>
@ -289,6 +293,11 @@
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.mockito</groupId> <groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId> <artifactId>mockito-core</artifactId>
@ -351,7 +360,12 @@
<systemPropertyVariables> <systemPropertyVariables>
<spring.config.name>thingsboard</spring.config.name> <spring.config.name>thingsboard</spring.config.name>
</systemPropertyVariables> </systemPropertyVariables>
<excludes>
<exclude>**/sql/*Test.java</exclude>
<exclude>**/nosql/*Test.java</exclude>
</excludes>
<includes> <includes>
<include>**/*Test.java</include>
<include>**/*TestSuite.java</include> <include>**/*TestSuite.java</include>
</includes> </includes>
</configuration> </configuration>

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

File diff suppressed because one or more lines are too long

16
application/src/main/data/json/system/widget_bundles/control_widgets.json

@ -18,8 +18,8 @@
"resources": [], "resources": [],
"templateHtml": "<div style=\"height: 100%; overflow-y: auto;\" id=\"device-terminal\"></div>", "templateHtml": "<div style=\"height: 100%; overflow-y: auto;\" id=\"device-terminal\"></div>",
"templateCss": ".cmd .cursor.blink {\n -webkit-animation-name: terminal-underline;\n -moz-animation-name: terminal-underline;\n -ms-animation-name: terminal-underline;\n animation-name: terminal-underline;\n}\n.terminal .inverted, .cmd .inverted {\n border-bottom-color: #aaa;\n}\n\n", "templateCss": ".cmd .cursor.blink {\n -webkit-animation-name: terminal-underline;\n -moz-animation-name: terminal-underline;\n -ms-animation-name: terminal-underline;\n animation-name: terminal-underline;\n}\n.terminal .inverted, .cmd .inverted {\n border-bottom-color: #aaa;\n}\n\n",
"controllerScript": "var requestTimeout = 500;\nvar requestPersistent = false;\n\nself.onInit = function() {\n var subscription = self.ctx.defaultSubscription;\n var rpcEnabled = subscription.rpcEnabled;\n var deviceName = 'Simulated';\n var prompt;\n if (subscription.targetDeviceName && subscription.targetDeviceName.length) {\n deviceName = subscription.targetDeviceName;\n }\n if (self.ctx.settings.requestTimeout) {\n requestTimeout = self.ctx.settings.requestTimeout;\n }\n if (self.ctx.settings.requestPersistent) {\n requestPersistent = self.ctx.settings.requestPersistent;\n }\n var greetings = 'Welcome to ThingsBoard RPC debug terminal.\\n\\n';\n if (!rpcEnabled) {\n greetings += 'Target device is not set!\\n\\n';\n prompt = '';\n } else {\n greetings += 'Current target device for RPC commands: [[b;#fff;]' + deviceName + ']\\n\\n';\n greetings += 'Please type [[b;#fff;]\\'help\\'] to see usage.\\n';\n prompt = '[[b;#8bc34a;]' + deviceName +']> ';\n }\n \n var terminal = $('#device-terminal', self.ctx.$container).terminal(\n function(command) {\n if (command !== '') {\n try {\n var localCommand = command.trim();\n var requestUUID = uuidv4();\n if (localCommand === 'help') {\n printUsage(this);\n } else {\n var spaceIndex = localCommand.indexOf(' ');\n if (spaceIndex === -1 && !localCommand.length) {\n this.error(\"Wrong number of arguments!\");\n this.echo(' ');\n } else {\n var params;\n if (spaceIndex === -1) {\n spaceIndex = localCommand.length;\n }\n var name = localCommand.substr(0, spaceIndex);\n var args = localCommand.substr(spaceIndex + 1);\n if (args.length) {\n try {\n params = JSON.parse(args);\n } catch (e) {\n params = args;\n }\n }\n performRpc(this, name, params, requestUUID);\n }\n }\n } catch(e) {\n this.error(new String(e));\n }\n } else {\n this.echo('');\n }\n }, {\n greetings: greetings,\n prompt: prompt,\n enabled: rpcEnabled\n });\n \n if (!rpcEnabled) {\n terminal.error('No RPC target detected!').pause();\n }\n}\n\n\nfunction printUsage(terminal) {\n var commandsListText = '\\n[[b;#fff;]Usage:]\\n';\n commandsListText += ' <method> [params body]]\\n\\n';\n commandsListText += '[[b;#fff;]Example 1:]\\n'; \n commandsListText += ' myRemoteMethod1 myText\\n\\n'; \n commandsListText += '[[b;#fff;]Example 2:]\\n'; \n commandsListText += ' myOtherRemoteMethod \"{\\\\\"key1\\\\\": 2, \\\\\"key2\\\\\": \\\\\"myVal\\\\\"}\"\\n'; \n terminal.echo(new String(commandsListText));\n}\n\n\nfunction performRpc(terminal, method, params, requestUUID) {\n terminal.pause();\n self.ctx.controlApi.sendTwoWayCommand(method, params, requestTimeout, requestPersistent, requestUUID).subscribe(\n function success(responseBody) {\n terminal.echo(JSON.stringify(responseBody));\n terminal.echo(' ');\n terminal.resume();\n },\n function fail() {\n var errorText = self.ctx.defaultSubscription.rpcErrorText;\n terminal.error(errorText);\n terminal.echo(' ');\n terminal.resume();\n }\n );\n}\n\n\nfunction uuidv4() {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n}\n\n \nself.onDestroy = function() {\n}", "controllerScript": "var requestTimeout = 500;\nvar requestPersistent = false;\nvar persistentPollingInterval = 5000;\n\nself.onInit = function() {\n var subscription = self.ctx.defaultSubscription;\n var rpcEnabled = subscription.rpcEnabled;\n var deviceName = 'Simulated';\n var prompt;\n if (subscription.targetDeviceName && subscription.targetDeviceName.length) {\n deviceName = subscription.targetDeviceName;\n }\n if (self.ctx.settings.requestTimeout) {\n requestTimeout = self.ctx.settings.requestTimeout;\n }\n if (self.ctx.settings.requestPersistent) {\n requestPersistent = self.ctx.settings.requestPersistent;\n }\n if (self.ctx.settings.persistentPollingInterval) {\n persistentPollingInterval = self.ctx.settings.persistentPollingInterval;\n }\n var greetings = 'Welcome to ThingsBoard RPC debug terminal.\\n\\n';\n if (!rpcEnabled) {\n greetings += 'Target device is not set!\\n\\n';\n prompt = '';\n } else {\n greetings += 'Current target device for RPC commands: [[b;#fff;]' + deviceName + ']\\n\\n';\n greetings += 'Please type [[b;#fff;]\\'help\\'] to see usage.\\n';\n prompt = '[[b;#8bc34a;]' + deviceName +']> ';\n }\n \n var terminal = $('#device-terminal', self.ctx.$container).terminal(\n function(command) {\n if (command !== '') {\n try {\n var localCommand = command.trim();\n var requestUUID = uuidv4();\n if (localCommand === 'help') {\n printUsage(this);\n } else {\n var spaceIndex = localCommand.indexOf(' ');\n if (spaceIndex === -1 && !localCommand.length) {\n this.error(\"Wrong number of arguments!\");\n this.echo(' ');\n } else {\n var params;\n if (spaceIndex === -1) {\n spaceIndex = localCommand.length;\n }\n var name = localCommand.substr(0, spaceIndex);\n var args = localCommand.substr(spaceIndex + 1);\n if (args.length) {\n try {\n params = JSON.parse(args);\n } catch (e) {\n params = args;\n }\n }\n performRpc(this, name, params, requestUUID);\n }\n }\n } catch(e) {\n this.error(new String(e));\n }\n } else {\n this.echo('');\n }\n }, {\n greetings: greetings,\n prompt: prompt,\n enabled: rpcEnabled\n });\n \n if (!rpcEnabled) {\n terminal.error('No RPC target detected!').pause();\n }\n}\n\n\nfunction printUsage(terminal) {\n var commandsListText = '\\n[[b;#fff;]Usage:]\\n';\n commandsListText += ' <method> [params body]]\\n\\n';\n commandsListText += '[[b;#fff;]Example 1:]\\n'; \n commandsListText += ' myRemoteMethod1 myText\\n\\n'; \n commandsListText += '[[b;#fff;]Example 2:]\\n'; \n commandsListText += ' myOtherRemoteMethod \"{\\\\\"key1\\\\\": 2, \\\\\"key2\\\\\": \\\\\"myVal\\\\\"}\"\\n'; \n terminal.echo(new String(commandsListText));\n}\n\n\nfunction performRpc(terminal, method, params, requestUUID) {\n terminal.pause();\n self.ctx.controlApi.sendTwoWayCommand(method, params, requestTimeout, requestPersistent, persistentPollingInterval, requestUUID).subscribe(\n function success(responseBody) {\n terminal.echo(JSON.stringify(responseBody));\n terminal.echo(' ');\n terminal.resume();\n },\n function fail() {\n var errorText = self.ctx.defaultSubscription.rpcErrorText;\n terminal.error(errorText);\n terminal.echo(' ');\n terminal.resume();\n }\n );\n}\n\n\nfunction uuidv4() {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n}\n\n \nself.onDestroy = function() {\n self.ctx.controlApi.completedCommand();\n}",
"settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"requestTimeout\": {\n \"title\": \"RPC request timeout (ms)\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"requestPersistent\": {\n \"title\": \"RPC request persistent\",\n \"type\": \"boolean\",\n \"default\": false\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"requestTimeout\",\n \"requestPersistent\"\n ]\n}", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"requestTimeout\": {\n \"title\": \"RPC request timeout (ms)\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"requestPersistent\": {\n \"title\": \"RPC request persistent\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"persistentPollingInterval\": {\n \"title\": \"Polling interval in milliseconds to get persistent RPC command response\",\n \"type\": \"number\",\n \"default\": 5000,\n \"minimum\": 1000\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"requestTimeout\",\n \"requestPersistent\",\n {\n \"key\": \"persistentPollingInterval\",\n \"condition\": \"model.requestPersistent === true\"\n }\n ]\n}",
"dataKeySettingsSchema": "{}\n", "dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":true,\"backgroundColor\":\"#010101\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"parseGpioStatusFunction\":\"return body[pin] === true;\",\"gpioStatusChangeRequest\":{\"method\":\"setGpioStatus\",\"paramsBody\":\"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"},\"requestTimeout\":500,\"switchPanelBackgroundColor\":\"#b71c1c\",\"gpioStatusRequest\":{\"method\":\"getGpioStatus\",\"paramsBody\":\"{}\"},\"gpioList\":[{\"pin\":1,\"label\":\"GPIO 1\",\"row\":0,\"col\":0,\"_uniqueKey\":0},{\"pin\":2,\"label\":\"GPIO 2\",\"row\":0,\"col\":1,\"_uniqueKey\":1},{\"pin\":3,\"label\":\"GPIO 3\",\"row\":1,\"col\":0,\"_uniqueKey\":2}]},\"title\":\"RPC debug terminal\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":true,\"backgroundColor\":\"#010101\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"parseGpioStatusFunction\":\"return body[pin] === true;\",\"gpioStatusChangeRequest\":{\"method\":\"setGpioStatus\",\"paramsBody\":\"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"},\"requestTimeout\":500,\"switchPanelBackgroundColor\":\"#b71c1c\",\"gpioStatusRequest\":{\"method\":\"getGpioStatus\",\"paramsBody\":\"{}\"},\"gpioList\":[{\"pin\":1,\"label\":\"GPIO 1\",\"row\":0,\"col\":0,\"_uniqueKey\":0},{\"pin\":2,\"label\":\"GPIO 2\",\"row\":0,\"col\":1,\"_uniqueKey\":1},{\"pin\":3,\"label\":\"GPIO 3\",\"row\":1,\"col\":0,\"_uniqueKey\":2}]},\"title\":\"RPC debug terminal\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
} }
@ -55,7 +55,7 @@
"templateHtml": "<tb-knob [ctx]='ctx'></tb-knob>", "templateHtml": "<tb-knob [ctx]='ctx'></tb-knob>",
"templateCss": "", "templateCss": "",
"controllerScript": "self.onInit = function() {\n}\n\nself.onResize = function() {\n}\n\nself.onDestroy = function() {\n}\n", "controllerScript": "self.onInit = function() {\n}\n\nself.onResize = function() {\n}\n\nself.onDestroy = function() {\n}\n",
"settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"minValue\": {\n \"title\": \"Minimum value\",\n \"type\": \"number\",\n \"default\": 0\n },\n \"maxValue\": {\n \"title\": \"Maximum value\",\n \"type\": \"number\",\n \"default\": 100\n },\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"number\",\n \"default\": 50\n },\n \"title\": {\n \"title\": \"Knob title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"getValueMethod\": {\n \"title\": \"Get value method\",\n \"type\": \"string\",\n \"default\": \"getValue\"\n },\n \"setValueMethod\": {\n \"title\": \"Set value method\",\n \"type\": \"string\",\n \"default\": \"setValue\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"minValue\", \"maxValue\", \"getValueMethod\", \"setValueMethod\", \"requestTimeout\"]\n },\n \"form\": [\n \"minValue\",\n \"maxValue\",\n \"initialValue\",\n \"title\",\n \"getValueMethod\",\n \"setValueMethod\",\n \"requestTimeout\"\n ]\n}", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"minValue\": {\n \"title\": \"Minimum value\",\n \"type\": \"number\",\n \"default\": 0\n },\n \"maxValue\": {\n \"title\": \"Maximum value\",\n \"type\": \"number\",\n \"default\": 100\n },\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"number\",\n \"default\": 50\n },\n \"title\": {\n \"title\": \"Knob title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"getValueMethod\": {\n \"title\": \"Get value method\",\n \"type\": \"string\",\n \"default\": \"getValue\"\n },\n \"setValueMethod\": {\n \"title\": \"Set value method\",\n \"type\": \"string\",\n \"default\": \"setValue\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"requestPersistent\": {\n \"title\": \"RPC request persistent\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"persistentPollingInterval\": {\n \"title\": \"Polling interval in milliseconds to get persistent RPC command response\",\n \"type\": \"number\",\n \"default\": 5000,\n \"minimum\": 1000\n }\n },\n \"required\": [\"minValue\", \"maxValue\", \"getValueMethod\", \"setValueMethod\", \"requestTimeout\"]\n },\n \"form\": [\n \"minValue\",\n \"maxValue\",\n \"initialValue\",\n \"title\",\n \"getValueMethod\",\n \"setValueMethod\",\n \"requestTimeout\",\n \"requestPersistent\",\n {\n \"key\": \"persistentPollingInterval\",\n \"condition\": \"model.requestPersistent === true\"\n }\n ]\n}",
"dataKeySettingsSchema": "{}\n", "dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"maxValue\":100,\"initialValue\":50,\"minValue\":0,\"title\":\"Knob control\",\"getValueMethod\":\"getValue\",\"setValueMethod\":\"setValue\"},\"title\":\"Knob Control\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}" "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"maxValue\":100,\"initialValue\":50,\"minValue\":0,\"title\":\"Knob control\",\"getValueMethod\":\"getValue\",\"setValueMethod\":\"setValue\"},\"title\":\"Knob Control\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}"
} }
@ -73,7 +73,7 @@
"templateHtml": "<tb-switch [ctx]='ctx'></tb-switch>", "templateHtml": "<tb-switch [ctx]='ctx'></tb-switch>",
"templateCss": "", "templateCss": "",
"controllerScript": "self.onInit = function() {\n}\n\nself.onResize = function() {\n}\n\nself.onDestroy = function() {\n}\n", "controllerScript": "self.onInit = function() {\n}\n\nself.onResize = function() {\n}\n\nself.onDestroy = function() {\n}\n",
"settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"title\": {\n \"title\": \"Switch title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showOnOffLabels\": {\n \"title\": \"Show on/off labels\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"retrieveValueMethod\": {\n \"title\": \"Retrieve on/off value using method\",\n \"type\": \"string\",\n \"default\": \"rpc\"\n },\n \"valueKey\": {\n \"title\": \"Attribute/Timeseries value key (only when subscribe for attribute/timeseries method)\",\n \"type\": \"string\",\n \"default\": \"value\"\n },\n \"getValueMethod\": {\n \"title\": \"RPC get value method\",\n \"type\": \"string\",\n \"default\": \"getValue\"\n },\n \"setValueMethod\": {\n \"title\": \"RPC set value method\",\n \"type\": \"string\",\n \"default\": \"setValue\"\n },\n \"parseValueFunction\": {\n \"title\": \"Parse value function, f(data), returns boolean\",\n \"type\": \"string\",\n \"default\": \"return data ? true : false;\"\n },\n \"convertValueFunction\": {\n \"title\": \"Convert value function, f(value), returns payload used by RPC set value method\",\n \"type\": \"string\",\n \"default\": \"return value;\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"initialValue\",\n \"title\",\n \"showOnOffLabels\",\n {\n \"key\": \"retrieveValueMethod\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"none\",\n \"label\": \"Don't retrieve\"\n },\n {\n \"value\": \"rpc\",\n \"label\": \"Call RPC get value method\"\n },\n {\n \"value\": \"attribute\",\n \"label\": \"Subscribe for attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Subscribe for timeseries\"\n }\n ]\n },\n \"valueKey\",\n \"getValueMethod\",\n \"setValueMethod\",\n {\n \"key\": \"parseValueFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"convertValueFunction\",\n \"type\": \"javascript\"\n },\n \"requestTimeout\"\n ]\n}", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"title\": {\n \"title\": \"Switch title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showOnOffLabels\": {\n \"title\": \"Show on/off labels\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"retrieveValueMethod\": {\n \"title\": \"Retrieve on/off value using method\",\n \"type\": \"string\",\n \"default\": \"rpc\"\n },\n \"valueKey\": {\n \"title\": \"Attribute/Timeseries value key (only when subscribe for attribute/timeseries method)\",\n \"type\": \"string\",\n \"default\": \"value\"\n },\n \"getValueMethod\": {\n \"title\": \"RPC get value method\",\n \"type\": \"string\",\n \"default\": \"getValue\"\n },\n \"setValueMethod\": {\n \"title\": \"RPC set value method\",\n \"type\": \"string\",\n \"default\": \"setValue\"\n },\n \"parseValueFunction\": {\n \"title\": \"Parse value function, f(data), returns boolean\",\n \"type\": \"string\",\n \"default\": \"return data ? true : false;\"\n },\n \"convertValueFunction\": {\n \"title\": \"Convert value function, f(value), returns payload used by RPC set value method\",\n \"type\": \"string\",\n \"default\": \"return value;\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"requestPersistent\": {\n \"title\": \"RPC request persistent\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"persistentPollingInterval\": {\n \"title\": \"Polling interval in milliseconds to get persistent RPC command response\",\n \"type\": \"number\",\n \"default\": 5000,\n \"minimum\": 1000\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"initialValue\",\n \"title\",\n \"showOnOffLabels\",\n {\n \"key\": \"retrieveValueMethod\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"none\",\n \"label\": \"Don't retrieve\"\n },\n {\n \"value\": \"rpc\",\n \"label\": \"Call RPC get value method\"\n },\n {\n \"value\": \"attribute\",\n \"label\": \"Subscribe for attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Subscribe for timeseries\"\n }\n ]\n },\n \"valueKey\",\n \"getValueMethod\",\n \"setValueMethod\",\n {\n \"key\": \"parseValueFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"convertValueFunction\",\n \"type\": \"javascript\"\n },\n \"requestTimeout\",\n \"requestPersistent\",\n {\n \"key\": \"persistentPollingInterval\",\n \"condition\": \"model.requestPersistent === true\"\n }\n ]\n}",
"dataKeySettingsSchema": "{}\n", "dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"initialValue\":false,\"getValueMethod\":\"getValue\",\"setValueMethod\":\"setValue\",\"showOnOffLabels\":true,\"title\":\"Switch control\"},\"title\":\"Switch Control\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}" "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"initialValue\":false,\"getValueMethod\":\"getValue\",\"setValueMethod\":\"setValue\",\"showOnOffLabels\":true,\"title\":\"Switch control\"},\"title\":\"Switch Control\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}"
} }
@ -91,7 +91,7 @@
"templateHtml": "<tb-round-switch [ctx]='ctx'></tb-round-switch>", "templateHtml": "<tb-round-switch [ctx]='ctx'></tb-round-switch>",
"templateCss": "", "templateCss": "",
"controllerScript": "self.onInit = function() {\n}\n\nself.onResize = function() {\n}\n\nself.onDestroy = function() {\n}\n", "controllerScript": "self.onInit = function() {\n}\n\nself.onResize = function() {\n}\n\nself.onDestroy = function() {\n}\n",
"settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"title\": {\n \"title\": \"Switch title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"retrieveValueMethod\": {\n \"title\": \"Retrieve on/off value using method\",\n \"type\": \"string\",\n \"default\": \"rpc\"\n },\n \"valueKey\": {\n \"title\": \"Attribute/Timeseries value key (only when subscribe for attribute/timeseries method)\",\n \"type\": \"string\",\n \"default\": \"value\"\n },\n \"getValueMethod\": {\n \"title\": \"RPC get value method\",\n \"type\": \"string\",\n \"default\": \"getValue\"\n },\n \"setValueMethod\": {\n \"title\": \"RPC set value method\",\n \"type\": \"string\",\n \"default\": \"setValue\"\n },\n \"parseValueFunction\": {\n \"title\": \"Parse value function, f(data), returns boolean\",\n \"type\": \"string\",\n \"default\": \"return data ? true : false;\"\n },\n \"convertValueFunction\": {\n \"title\": \"Convert value function, f(value), returns payload used by RPC set value method\",\n \"type\": \"string\",\n \"default\": \"return value;\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"initialValue\",\n \"title\",\n {\n \"key\": \"retrieveValueMethod\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"none\",\n \"label\": \"Don't retrieve\"\n },\n {\n \"value\": \"rpc\",\n \"label\": \"Call RPC get value method\"\n },\n {\n \"value\": \"attribute\",\n \"label\": \"Subscribe for attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Subscribe for timeseries\"\n }\n ]\n },\n \"valueKey\",\n \"getValueMethod\",\n \"setValueMethod\",\n {\n \"key\": \"parseValueFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"convertValueFunction\",\n \"type\": \"javascript\"\n },\n \"requestTimeout\"\n ]\n}", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"title\": {\n \"title\": \"Switch title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"retrieveValueMethod\": {\n \"title\": \"Retrieve on/off value using method\",\n \"type\": \"string\",\n \"default\": \"rpc\"\n },\n \"valueKey\": {\n \"title\": \"Attribute/Timeseries value key (only when subscribe for attribute/timeseries method)\",\n \"type\": \"string\",\n \"default\": \"value\"\n },\n \"getValueMethod\": {\n \"title\": \"RPC get value method\",\n \"type\": \"string\",\n \"default\": \"getValue\"\n },\n \"setValueMethod\": {\n \"title\": \"RPC set value method\",\n \"type\": \"string\",\n \"default\": \"setValue\"\n },\n \"parseValueFunction\": {\n \"title\": \"Parse value function, f(data), returns boolean\",\n \"type\": \"string\",\n \"default\": \"return data ? true : false;\"\n },\n \"convertValueFunction\": {\n \"title\": \"Convert value function, f(value), returns payload used by RPC set value method\",\n \"type\": \"string\",\n \"default\": \"return value;\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"requestPersistent\": {\n \"title\": \"RPC request persistent\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"persistentPollingInterval\": {\n \"title\": \"Polling interval in milliseconds to get persistent RPC command response\",\n \"type\": \"number\",\n \"default\": 5000,\n \"minimum\": 1000\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"initialValue\",\n \"title\",\n {\n \"key\": \"retrieveValueMethod\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"none\",\n \"label\": \"Don't retrieve\"\n },\n {\n \"value\": \"rpc\",\n \"label\": \"Call RPC get value method\"\n },\n {\n \"value\": \"attribute\",\n \"label\": \"Subscribe for attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Subscribe for timeseries\"\n }\n ]\n },\n \"valueKey\",\n \"getValueMethod\",\n \"setValueMethod\",\n {\n \"key\": \"parseValueFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"convertValueFunction\",\n \"type\": \"javascript\"\n },\n \"requestTimeout\",\n \"requestPersistent\",\n {\n \"key\": \"persistentPollingInterval\",\n \"condition\": \"model.requestPersistent === true\"\n }\n ]\n}",
"dataKeySettingsSchema": "{}\n", "dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"initialValue\":false,\"getValueMethod\":\"getValue\",\"setValueMethod\":\"setValue\",\"title\":\"Round switch\",\"retrieveValueMethod\":\"rpc\",\"valueKey\":\"value\",\"parseValueFunction\":\"return data ? true : false;\",\"convertValueFunction\":\"return value;\"},\"title\":\"Round switch\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}" "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"initialValue\":false,\"getValueMethod\":\"getValue\",\"setValueMethod\":\"setValue\",\"title\":\"Round switch\",\"retrieveValueMethod\":\"rpc\",\"valueKey\":\"value\",\"parseValueFunction\":\"return data ? true : false;\",\"convertValueFunction\":\"return value;\"},\"title\":\"Round switch\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}"
} }
@ -109,7 +109,7 @@
"templateHtml": "<tb-led-indicator [ctx]='ctx'></tb-led-indicator>", "templateHtml": "<tb-led-indicator [ctx]='ctx'></tb-led-indicator>",
"templateCss": "", "templateCss": "",
"controllerScript": "self.onInit = function() {\n}\n\nself.onResize = function() {\n}\n\nself.onDestroy = function() {\n}\n", "controllerScript": "self.onInit = function() {\n}\n\nself.onResize = function() {\n}\n\nself.onDestroy = function() {\n}\n",
"settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"title\": {\n \"title\": \"LED title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"ledColor\": {\n \"title\": \"LED Color\",\n \"type\": \"string\",\n \"default\": \"green\"\n },\n \"performCheckStatus\": {\n \"title\": \"Perform RPC device status check\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"checkStatusMethod\": {\n \"title\": \"RPC check device status method\",\n \"type\": \"string\",\n \"default\": \"checkStatus\"\n },\n \"retrieveValueMethod\": {\n \"title\": \"Retrieve led status value using method\",\n \"type\": \"string\",\n \"default\": \"attribute\"\n },\n \"valueAttribute\": {\n \"title\": \"Device attribute/timeseries containing led status value\",\n \"type\": \"string\",\n \"default\": \"value\"\n },\n \"parseValueFunction\": {\n \"title\": \"Parse led status value function, f(data), returns boolean\",\n \"type\": \"string\",\n \"default\": \"return data ? true : false;\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout (ms)\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"valueAttribute\", \"requestTimeout\"]\n },\n \"form\": [\n \"initialValue\",\n \"title\",\n {\n \"key\": \"ledColor\",\n \"type\": \"color\"\n },\n \"performCheckStatus\",\n \"checkStatusMethod\",\n {\n \"key\": \"retrieveValueMethod\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"attribute\",\n \"label\": \"Subscribe for attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Subscribe for timeseries\"\n }\n ]\n },\n \"valueAttribute\",\n {\n \"key\": \"parseValueFunction\",\n \"type\": \"javascript\"\n },\n \"requestTimeout\"\n ]\n}", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"title\": {\n \"title\": \"LED title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"ledColor\": {\n \"title\": \"LED Color\",\n \"type\": \"string\",\n \"default\": \"green\"\n },\n \"performCheckStatus\": {\n \"title\": \"Perform RPC device status check\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"checkStatusMethod\": {\n \"title\": \"RPC check device status method\",\n \"type\": \"string\",\n \"default\": \"checkStatus\"\n },\n \"retrieveValueMethod\": {\n \"title\": \"Retrieve led status value using method\",\n \"type\": \"string\",\n \"default\": \"attribute\"\n },\n \"valueAttribute\": {\n \"title\": \"Device attribute/timeseries containing led status value\",\n \"type\": \"string\",\n \"default\": \"value\"\n },\n \"parseValueFunction\": {\n \"title\": \"Parse led status value function, f(data), returns boolean\",\n \"type\": \"string\",\n \"default\": \"return data ? true : false;\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout (ms)\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"requestPersistent\": {\n \"title\": \"RPC request persistent\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"persistentPollingInterval\": {\n \"title\": \"Polling interval in milliseconds to get persistent RPC command response\",\n \"type\": \"number\",\n \"default\": 5000,\n \"minimum\": 1000\n }\n },\n \"required\": [\"valueAttribute\", \"requestTimeout\"]\n },\n \"form\": [\n \"initialValue\",\n \"title\",\n {\n \"key\": \"ledColor\",\n \"type\": \"color\"\n },\n \"performCheckStatus\",\n \"checkStatusMethod\",\n {\n \"key\": \"retrieveValueMethod\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"attribute\",\n \"label\": \"Subscribe for attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Subscribe for timeseries\"\n }\n ]\n },\n \"valueAttribute\",\n {\n \"key\": \"parseValueFunction\",\n \"type\": \"javascript\"\n },\n \"requestTimeout\",\n \"requestPersistent\",\n {\n \"key\": \"persistentPollingInterval\",\n \"condition\": \"model.requestPersistent === true\"\n }\n ]\n}",
"dataKeySettingsSchema": "{}\n", "dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"initialValue\":true,\"title\":\"Led indicator\",\"ledColor\":\"#4caf50\",\"valueAttribute\":\"value\",\"retrieveValueMethod\":\"attribute\",\"parseValueFunction\":\"return data ? true : false;\",\"performCheckStatus\":true,\"checkStatusMethod\":\"checkStatus\"},\"title\":\"Led indicator\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}" "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"initialValue\":true,\"title\":\"Led indicator\",\"ledColor\":\"#4caf50\",\"valueAttribute\":\"value\",\"retrieveValueMethod\":\"attribute\",\"parseValueFunction\":\"return data ? true : false;\",\"performCheckStatus\":true,\"checkStatusMethod\":\"checkStatus\"},\"title\":\"Led indicator\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}"
} }
@ -126,8 +126,8 @@
"resources": [], "resources": [],
"templateHtml": "<div class=\"tb-rpc-button\" fxLayout=\"column\">\n <div fxFlex=\"20\" class=\"title-container\" fxLayout=\"row\"\n fxLayoutAlign=\"center center\" [fxShow]=\"showTitle\">\n <span class=\"button-title\">{{title}}</span>\n </div>\n <div fxFlex=\"{{showTitle ? 80 : 100}}\" [ngStyle]=\"{paddingTop: showTitle ? '5px': '10px'}\"\n class=\"button-container\" fxLayout=\"column\" fxLayoutAlign=\"center center\">\n <div>\n <button mat-button (click)=\"sendCommand()\"\n [class.mat-raised-button]=\"styleButton?.isRaised\"\n [color]=\"styleButton?.isPrimary ? 'primary' : ''\"\n [ngStyle]=\"customStyle\">\n {{buttonLable}}\n </button>\n </div>\n </div>\n <div class=\"error-container\" [ngStyle]=\"{'background': error?.length ? 'rgba(255,255,255,0.25)' : 'none'}\"\n fxLayout=\"row\" fxLayoutAlign=\"center center\">\n <span class=\"button-error\">{{ error }}</span>\n </div>\n</div>", "templateHtml": "<div class=\"tb-rpc-button\" fxLayout=\"column\">\n <div fxFlex=\"20\" class=\"title-container\" fxLayout=\"row\"\n fxLayoutAlign=\"center center\" [fxShow]=\"showTitle\">\n <span class=\"button-title\">{{title}}</span>\n </div>\n <div fxFlex=\"{{showTitle ? 80 : 100}}\" [ngStyle]=\"{paddingTop: showTitle ? '5px': '10px'}\"\n class=\"button-container\" fxLayout=\"column\" fxLayoutAlign=\"center center\">\n <div>\n <button mat-button (click)=\"sendCommand()\"\n [class.mat-raised-button]=\"styleButton?.isRaised\"\n [color]=\"styleButton?.isPrimary ? 'primary' : ''\"\n [ngStyle]=\"customStyle\">\n {{buttonLable}}\n </button>\n </div>\n </div>\n <div class=\"error-container\" [ngStyle]=\"{'background': error?.length ? 'rgba(255,255,255,0.25)' : 'none'}\"\n fxLayout=\"row\" fxLayoutAlign=\"center center\">\n <span class=\"button-error\">{{ error }}</span>\n </div>\n</div>",
"templateCss": ".tb-rpc-button {\n width: 100%;\n height: 100%;\n}\n\n.tb-rpc-button .title-container {\n font-weight: 500;\n white-space: nowrap;\n margin: 10px 0;\n}\n\n.tb-rpc-button .button-container div{\n min-width: 80%\n}\n\n.tb-rpc-button .button-container .mat-button{\n width: 100%;\n margin: 0;\n}\n\n.tb-rpc-button .error-container {\n position: absolute;\n top: 2%;\n right: 0;\n left: 0;\n z-index: 4;\n height: 14px;\n}\n\n.tb-rpc-button .error-container .button-error {\n color: #ff3315;\n white-space: nowrap;\n}", "templateCss": ".tb-rpc-button {\n width: 100%;\n height: 100%;\n}\n\n.tb-rpc-button .title-container {\n font-weight: 500;\n white-space: nowrap;\n margin: 10px 0;\n}\n\n.tb-rpc-button .button-container div{\n min-width: 80%\n}\n\n.tb-rpc-button .button-container .mat-button{\n width: 100%;\n margin: 0;\n}\n\n.tb-rpc-button .error-container {\n position: absolute;\n top: 2%;\n right: 0;\n left: 0;\n z-index: 4;\n height: 14px;\n}\n\n.tb-rpc-button .error-container .button-error {\n color: #ff3315;\n white-space: nowrap;\n}",
"controllerScript": "self.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges();\n });\n};\n\nfunction init() {\n let rpcEnabled = self.ctx.defaultSubscription.rpcEnabled;\n\n self.ctx.$scope.buttonLable = self.ctx.settings.buttonText;\n self.ctx.$scope.showTitle = self.ctx.settings.title &&\n self.ctx.settings.title.length ? true : false;\n self.ctx.$scope.title = self.ctx.settings.title;\n self.ctx.$scope.styleButton = self.ctx.settings.styleButton;\n\n if (self.ctx.settings.styleButton.isPrimary ===\n false) {\n self.ctx.$scope.customStyle = {\n 'background-color': self.ctx.$scope.styleButton.bgColor,\n 'color': self.ctx.$scope.styleButton.textColor\n };\n }\n\n if (!rpcEnabled) {\n self.ctx.$scope.error =\n 'Target device is not set!';\n }\n\n self.ctx.$scope.sendCommand = function() {\n var rpcMethod = self.ctx.settings.methodName;\n var rpcParams = self.ctx.settings.methodParams;\n var timeout = self.ctx.settings.requestTimeout;\n var oneWayElseTwoWay = self.ctx.settings.oneWayElseTwoWay ?\n true : false;\n\n var commandPromise;\n if (oneWayElseTwoWay) {\n commandPromise = self.ctx.controlApi.sendOneWayCommand(\n rpcMethod, rpcParams, timeout);\n } else {\n commandPromise = self.ctx.controlApi.sendTwoWayCommand(\n rpcMethod, rpcParams, timeout);\n }\n commandPromise.subscribe(\n function success() {\n self.ctx.$scope.error = \"\";\n self.ctx.detectChanges();\n },\n function fail(rejection) {\n if (self.ctx.settings.showError) {\n self.ctx.$scope.error =\n rejection.status + \": \" +\n rejection.statusText;\n self.ctx.detectChanges();\n }\n }\n );\n };\n}\n", "controllerScript": "var requestPersistent = false;\nvar persistentPollingInterval = 5000;\n\nself.onInit = function() {\n if (self.ctx.settings.requestPersistent) {\n requestPersistent = self.ctx.settings.requestPersistent;\n }\n if (self.ctx.settings.persistentPollingInterval) {\n persistentPollingInterval = self.ctx.settings.persistentPollingInterval;\n }\n \n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges();\n });\n};\n\nfunction init() {\n let rpcEnabled = self.ctx.defaultSubscription.rpcEnabled;\n\n self.ctx.$scope.buttonLable = self.ctx.settings.buttonText;\n self.ctx.$scope.showTitle = self.ctx.settings.title &&\n self.ctx.settings.title.length ? true : false;\n self.ctx.$scope.title = self.ctx.settings.title;\n self.ctx.$scope.styleButton = self.ctx.settings.styleButton;\n\n if (self.ctx.settings.styleButton.isPrimary ===\n false) {\n self.ctx.$scope.customStyle = {\n 'background-color': self.ctx.$scope.styleButton.bgColor,\n 'color': self.ctx.$scope.styleButton.textColor\n };\n }\n\n if (!rpcEnabled) {\n self.ctx.$scope.error =\n 'Target device is not set!';\n }\n\n self.ctx.$scope.sendCommand = function() {\n var rpcMethod = self.ctx.settings.methodName;\n var rpcParams = self.ctx.settings.methodParams;\n if (rpcParams.length) {\n try {\n rpcParams = JSON.parse(rpcParams);\n } catch (e) {}\n }\n var timeout = self.ctx.settings.requestTimeout;\n var oneWayElseTwoWay = self.ctx.settings.oneWayElseTwoWay ?\n true : false;\n\n var commandPromise;\n if (oneWayElseTwoWay) {\n commandPromise = self.ctx.controlApi.sendOneWayCommand(\n rpcMethod, rpcParams, timeout, requestPersistent, persistentPollingInterval);\n } else {\n commandPromise = self.ctx.controlApi.sendTwoWayCommand(\n rpcMethod, rpcParams, timeout, requestPersistent, persistentPollingInterval);\n }\n commandPromise.subscribe(\n function success() {\n self.ctx.$scope.error = \"\";\n self.ctx.detectChanges();\n },\n function fail(rejection) {\n if (self.ctx.settings.showError) {\n self.ctx.$scope.error =\n rejection.status + \": \" +\n rejection.statusText;\n self.ctx.detectChanges();\n }\n }\n );\n };\n}\n\nself.onDestroy = function() {\n self.ctx.controlApi.completedCommand();\n}\n",
"settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"title\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"buttonText\": {\n \"title\": \"Button label\",\n \"type\": \"string\",\n \"default\": \"Send RPC\"\n },\n \"oneWayElseTwoWay\": {\n \"title\": \"Is One Way Command\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"showError\": {\n \"title\": \"Show RPC command execution error\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"methodName\": {\n \"title\": \"RPC method\",\n \"type\": \"string\",\n \"default\": \"rpcCommand\"\n },\n \"methodParams\": {\n \"title\": \"RPC method params\",\n \"type\": \"string\",\n \"default\": \"{}\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 5000\n },\n \"styleButton\": {\n \"type\": \"object\",\n \"title\": \"Button Style\",\n \"properties\": {\n \"isRaised\": {\n \"type\": \"boolean\",\n \"title\": \"Raised\",\n \"default\": true\n },\n \"isPrimary\": {\n \"type\": \"boolean\",\n \"title\": \"Primary color\",\n \"default\": false\n },\n \"bgColor\": {\n \"type\": \"string\",\n \"title\": \"Button background color\",\n \"default\": null\n },\n \"textColor\": {\n \"type\": \"string\",\n \"title\": \"Button text color\",\n \"default\": null\n }\n }\n },\n \"required\": []\n }\n },\n \"form\": [\n \"title\",\n \"buttonText\",\n \"oneWayElseTwoWay\",\n \"showError\",\n \"methodName\",\n {\n \"key\": \"methodParams\",\n \"type\": \"json\"\n },\n \"requestTimeout\",\n {\n \"key\": \"styleButton\",\n \"items\": [\n \"styleButton.isRaised\",\n \"styleButton.isPrimary\",\n {\n \"key\": \"styleButton.bgColor\",\n \"type\": \"color\"\n },\n {\n \"key\": \"styleButton.textColor\",\n \"type\": \"color\"\n }\n ]\n }\n ]\n\n}", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"title\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"buttonText\": {\n \"title\": \"Button label\",\n \"type\": \"string\",\n \"default\": \"Send RPC\"\n },\n \"oneWayElseTwoWay\": {\n \"title\": \"Is One Way Command\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"showError\": {\n \"title\": \"Show RPC command execution error\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"methodName\": {\n \"title\": \"RPC method\",\n \"type\": \"string\",\n \"default\": \"rpcCommand\"\n },\n \"methodParams\": {\n \"title\": \"RPC method params\",\n \"type\": \"string\",\n \"default\": \"{}\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 5000\n },\n \"requestPersistent\": {\n \"title\": \"RPC request persistent\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"persistentPollingInterval\": {\n \"title\": \"Polling interval in milliseconds to get persistent RPC command response\",\n \"type\": \"number\",\n \"default\": 5000,\n \"minimum\": 1000\n },\n \"styleButton\": {\n \"type\": \"object\",\n \"title\": \"Button Style\",\n \"properties\": {\n \"isRaised\": {\n \"type\": \"boolean\",\n \"title\": \"Raised\",\n \"default\": true\n },\n \"isPrimary\": {\n \"type\": \"boolean\",\n \"title\": \"Primary color\",\n \"default\": false\n },\n \"bgColor\": {\n \"type\": \"string\",\n \"title\": \"Button background color\",\n \"default\": null\n },\n \"textColor\": {\n \"type\": \"string\",\n \"title\": \"Button text color\",\n \"default\": null\n }\n }\n },\n \"required\": []\n }\n },\n \"form\": [\n \"title\",\n \"buttonText\",\n \"oneWayElseTwoWay\",\n \"showError\",\n \"methodName\",\n {\n \"key\": \"methodParams\",\n \"type\": \"json\"\n },\n \"requestTimeout\",\n \"requestPersistent\",\n {\n \"key\": \"persistentPollingInterval\",\n \"condition\": \"model.requestPersistent === true\"\n },\n {\n \"key\": \"styleButton\",\n \"items\": [\n \"styleButton.isRaised\",\n \"styleButton.isPrimary\",\n {\n \"key\": \"styleButton.bgColor\",\n \"type\": \"color\"\n },\n {\n \"key\": \"styleButton.textColor\",\n \"type\": \"color\"\n }\n ]\n }\n ]\n\n}",
"dataKeySettingsSchema": "{}\n", "dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":5000,\"oneWayElseTwoWay\":true,\"buttonText\":\"Send RPC\",\"styleButton\":{\"isRaised\":true,\"isPrimary\":false},\"methodName\":\"rpcCommand\",\"methodParams\":\"{}\"},\"title\":\"RPC Button\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":5000,\"oneWayElseTwoWay\":true,\"buttonText\":\"Send RPC\",\"styleButton\":{\"isRaised\":true,\"isPrimary\":false},\"methodName\":\"rpcCommand\",\"methodParams\":\"{}\"},\"title\":\"RPC Button\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
} }

5
application/src/main/data/json/system/widget_bundles/edge_widgets.json

File diff suppressed because one or more lines are too long

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

File diff suppressed because one or more lines are too long

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

File diff suppressed because one or more lines are too long

7
application/src/main/data/upgrade/3.2.2/schema_update.sql

@ -67,6 +67,7 @@ CREATE TABLE IF NOT EXISTS ota_package (
type varchar(32) NOT NULL, type varchar(32) NOT NULL,
title varchar(255) NOT NULL, title varchar(255) NOT NULL,
version varchar(255) NOT NULL, version varchar(255) NOT NULL,
tag varchar(255),
url varchar(255), url varchar(255),
file_name varchar(255), file_name varchar(255),
content_type varchar(255), content_type varchar(255),
@ -142,7 +143,9 @@ CREATE TABLE IF NOT EXISTS oauth2_mobile (
); );
ALTER TABLE dashboard ALTER TABLE dashboard
ADD COLUMN IF NOT EXISTS image varchar(1000000); ADD COLUMN IF NOT EXISTS image varchar(1000000),
ADD COLUMN IF NOT EXISTS mobile_hide boolean DEFAULT false,
ADD COLUMN IF NOT EXISTS mobile_order int;
ALTER TABLE device_profile ALTER TABLE device_profile
ADD COLUMN IF NOT EXISTS image varchar(1000000), ADD COLUMN IF NOT EXISTS image varchar(1000000),
@ -206,8 +209,8 @@ CREATE TABLE IF NOT EXISTS rpc (
expiration_time bigint NOT NULL, expiration_time bigint NOT NULL,
request varchar(10000000) NOT NULL, request varchar(10000000) NOT NULL,
response varchar(10000000), response varchar(10000000),
additional_info varchar(10000000),
status varchar(255) NOT NULL status varchar(255) NOT NULL
); );
CREATE INDEX IF NOT EXISTS idx_rpc_tenant_id_device_id ON rpc(tenant_id, device_id); CREATE INDEX IF NOT EXISTS idx_rpc_tenant_id_device_id ON rpc(tenant_id, device_id);

14
application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java

@ -81,7 +81,7 @@ import org.thingsboard.server.service.executors.ExternalCallExecutorService;
import org.thingsboard.server.service.executors.SharedEventLoopGroupService; import org.thingsboard.server.service.executors.SharedEventLoopGroupService;
import org.thingsboard.server.service.mail.MailExecutorService; import org.thingsboard.server.service.mail.MailExecutorService;
import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService; import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService;
import org.thingsboard.server.service.rpc.TbRpcService; import org.thingsboard.server.service.rpc.TbRpcService;
import org.thingsboard.server.service.rpc.TbRuleEngineDeviceRpcService; import org.thingsboard.server.service.rpc.TbRuleEngineDeviceRpcService;
@ -265,7 +265,8 @@ public class ActorSystemContext {
@Getter @Getter
private SmsSenderFactory smsSenderFactory; private SmsSenderFactory smsSenderFactory;
@Autowired @Lazy
@Autowired(required = false)
@Getter @Getter
private ClaimDevicesService claimDevicesService; private ClaimDevicesService claimDevicesService;
@ -399,6 +400,14 @@ public class ActorSystemContext {
@Getter @Getter
private String debugPerTenantLimitsConfiguration; private String debugPerTenantLimitsConfiguration;
@Value("${actors.rpc.sequential:false}")
@Getter
private boolean rpcSequential;
@Value("${actors.rpc.max_retries:5}")
@Getter
private int maxRpcRetries;
@Getter @Getter
@Setter @Setter
private TbActorSystem actorSystem; private TbActorSystem actorSystem;
@ -477,7 +486,6 @@ public class ActorSystemContext {
return partitionService.resolve(serviceType, queueName, tenantId, entityId); return partitionService.resolve(serviceType, queueName, tenantId, entityId);
} }
public String getServiceId() { public String getServiceId() {
return serviceInfoProvider.getServiceId(); return serviceInfoProvider.getServiceId();
} }

5
application/src/main/java/org/thingsboard/server/actors/app/AppActor.java

@ -55,7 +55,7 @@ public class AppActor extends ContextAwareActor {
private final TbTenantProfileCache tenantProfileCache; private final TbTenantProfileCache tenantProfileCache;
private final TenantService tenantService; private final TenantService tenantService;
private final Set<TenantId> deletedTenants; private final Set<TenantId> deletedTenants;
private boolean ruleChainsInitialized; private volatile boolean ruleChainsInitialized;
private AppActor(ActorSystemContext systemContext) { private AppActor(ActorSystemContext systemContext) {
super(systemContext); super(systemContext);
@ -69,7 +69,7 @@ public class AppActor extends ContextAwareActor {
if (!ruleChainsInitialized) { if (!ruleChainsInitialized) {
initTenantActors(); initTenantActors();
ruleChainsInitialized = true; ruleChainsInitialized = true;
if (msg.getMsgType() != MsgType.APP_INIT_MSG) { if (msg.getMsgType() != MsgType.APP_INIT_MSG && msg.getMsgType() != MsgType.PARTITION_CHANGE_MSG) {
log.warn("Rule Chains initialized by unexpected message: {}", msg); log.warn("Rule Chains initialized by unexpected message: {}", msg);
} }
} }
@ -95,6 +95,7 @@ public class AppActor extends ContextAwareActor {
case DEVICE_RPC_REQUEST_TO_DEVICE_ACTOR_MSG: case DEVICE_RPC_REQUEST_TO_DEVICE_ACTOR_MSG:
case DEVICE_RPC_RESPONSE_TO_DEVICE_ACTOR_MSG: case DEVICE_RPC_RESPONSE_TO_DEVICE_ACTOR_MSG:
case SERVER_RPC_RESPONSE_TO_DEVICE_ACTOR_MSG: case SERVER_RPC_RESPONSE_TO_DEVICE_ACTOR_MSG:
case REMOVE_RPC_TO_DEVICE_ACTOR_MSG:
onToDeviceActorMsg((TenantAwareMsg) msg, true); onToDeviceActorMsg((TenantAwareMsg) msg, true);
break; break;
case EDGE_EVENT_UPDATE_TO_EDGE_SESSION_MSG: case EDGE_EVENT_UPDATE_TO_EDGE_SESSION_MSG:

4
application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java

@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.TbActorMsg; import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.timeout.DeviceActorServerSideRpcTimeoutMsg; import org.thingsboard.server.common.msg.timeout.DeviceActorServerSideRpcTimeoutMsg;
import org.thingsboard.server.service.rpc.FromDeviceRpcResponseActorMsg; import org.thingsboard.server.service.rpc.FromDeviceRpcResponseActorMsg;
import org.thingsboard.server.service.rpc.RemoveRpcActorMsg;
import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg; import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg;
import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper; import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper;
@ -84,6 +85,9 @@ public class DeviceActor extends ContextAwareActor {
case DEVICE_EDGE_UPDATE_TO_DEVICE_ACTOR_MSG: case DEVICE_EDGE_UPDATE_TO_DEVICE_ACTOR_MSG:
processor.processEdgeUpdate((DeviceEdgeUpdateMsg) msg); processor.processEdgeUpdate((DeviceEdgeUpdateMsg) msg);
break; break;
case REMOVE_RPC_TO_DEVICE_ACTOR_MSG:
processor.processRemoveRpc(ctx, (RemoveRpcActorMsg) msg);
break;
default: default:
return false; return false;
} }

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

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.actors.device; package org.thingsboard.server.actors.device;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
@ -25,7 +26,6 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.LinkedHashMapRemoveEldest; import org.thingsboard.common.util.LinkedHashMapRemoveEldest;
import org.thingsboard.rule.engine.api.RpcError;
import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg; import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg;
import org.thingsboard.rule.engine.api.msg.DeviceCredentialsUpdateNotificationMsg; import org.thingsboard.rule.engine.api.msg.DeviceCredentialsUpdateNotificationMsg;
import org.thingsboard.rule.engine.api.msg.DeviceEdgeUpdateMsg; import org.thingsboard.rule.engine.api.msg.DeviceEdgeUpdateMsg;
@ -35,6 +35,7 @@ import org.thingsboard.server.actors.TbActorCtx;
import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor; import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.edge.EdgeEventType;
@ -47,9 +48,11 @@ import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.KvEntry;
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.SortOrder;
import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rpc.Rpc; import org.thingsboard.server.common.data.rpc.Rpc;
import org.thingsboard.server.common.data.rpc.RpcError;
import org.thingsboard.server.common.data.rpc.RpcStatus; import org.thingsboard.server.common.data.rpc.RpcStatus;
import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody; import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentials;
@ -57,8 +60,10 @@ import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.common.msg.TbActorMsg; import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse;
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
import org.thingsboard.server.common.msg.timeout.DeviceActorServerSideRpcTimeoutMsg; import org.thingsboard.server.common.msg.timeout.DeviceActorServerSideRpcTimeoutMsg;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceSessionsCacheEntry; import org.thingsboard.server.gen.transport.TransportProtos.DeviceSessionsCacheEntry;
@ -75,16 +80,16 @@ import org.thingsboard.server.gen.transport.TransportProtos.SessionType;
import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToAttributeUpdatesMsg; import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToAttributeUpdatesMsg;
import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToRPCMsg; import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToRPCMsg;
import org.thingsboard.server.gen.transport.TransportProtos.SubscriptionInfoProto; import org.thingsboard.server.gen.transport.TransportProtos.SubscriptionInfoProto;
import org.thingsboard.server.gen.transport.TransportProtos.ToDevicePersistedRpcResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseStatusMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToTransportUpdateCredentialsProto; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportUpdateCredentialsProto;
import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg;
import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto; import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto;
import org.thingsboard.server.service.rpc.FromDeviceRpcResponse;
import org.thingsboard.server.service.rpc.FromDeviceRpcResponseActorMsg; import org.thingsboard.server.service.rpc.FromDeviceRpcResponseActorMsg;
import org.thingsboard.server.service.rpc.RemoveRpcActorMsg;
import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg; import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg;
import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper; import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper;
@ -117,6 +122,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
private final Map<UUID, SessionInfo> attributeSubscriptions; private final Map<UUID, SessionInfo> attributeSubscriptions;
private final Map<UUID, SessionInfo> rpcSubscriptions; private final Map<UUID, SessionInfo> rpcSubscriptions;
private final Map<Integer, ToDeviceRpcRequestMetadata> toDeviceRpcPendingMap; private final Map<Integer, ToDeviceRpcRequestMetadata> toDeviceRpcPendingMap;
private final boolean rpcSequential;
private int rpcSeq = 0; private int rpcSeq = 0;
private String deviceName; private String deviceName;
@ -128,9 +134,10 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
super(systemContext); super(systemContext);
this.tenantId = tenantId; this.tenantId = tenantId;
this.deviceId = deviceId; this.deviceId = deviceId;
this.rpcSequential = systemContext.isRpcSequential();
this.attributeSubscriptions = new HashMap<>(); this.attributeSubscriptions = new HashMap<>();
this.rpcSubscriptions = new HashMap<>(); this.rpcSubscriptions = new HashMap<>();
this.toDeviceRpcPendingMap = new HashMap<>(); this.toDeviceRpcPendingMap = new LinkedHashMap<>();
this.sessions = new LinkedHashMapRemoveEldest<>(systemContext.getMaxConcurrentSessionsPerDevice(), this::notifyTransportAboutClosedSessionMaxSessionsLimit); this.sessions = new LinkedHashMapRemoveEldest<>(systemContext.getMaxConcurrentSessionsPerDevice(), this::notifyTransportAboutClosedSessionMaxSessionsLimit);
if (initAttributes()) { if (initAttributes()) {
restoreSessions(); restoreSessions();
@ -181,19 +188,19 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
if (timeout <= 0) { if (timeout <= 0) {
log.debug("[{}][{}] Ignoring message due to exp time reached, {}", deviceId, request.getId(), request.getExpirationTime()); log.debug("[{}][{}] Ignoring message due to exp time reached, {}", deviceId, request.getId(), request.getExpirationTime());
if (persisted) { if (persisted) {
createRpc(request, RpcStatus.TIMEOUT); createRpc(request, RpcStatus.EXPIRED);
} }
return; return;
} else if (persisted) { } else if (persisted) {
createRpc(request, RpcStatus.QUEUED); createRpc(request, RpcStatus.QUEUED);
} }
boolean sent; boolean sent = false;
if (systemContext.isEdgesEnabled() && edgeId != null) { if (systemContext.isEdgesEnabled() && edgeId != null) {
log.debug("[{}][{}] device is related to edge [{}]. Saving RPC request to edge queue", tenantId, deviceId, edgeId.getId()); log.debug("[{}][{}] device is related to edge [{}]. Saving RPC request to edge queue", tenantId, deviceId, edgeId.getId());
saveRpcRequestToEdgeQueue(request, rpcRequest.getRequestId()); saveRpcRequestToEdgeQueue(request, rpcRequest.getRequestId());
sent = true; sent = true;
} else { } else if (isSendNewRpcAvailable()) {
sent = rpcSubscriptions.size() > 0; sent = rpcSubscriptions.size() > 0;
Set<UUID> syncSessionSet = new HashSet<>(); Set<UUID> syncSessionSet = new HashSet<>();
rpcSubscriptions.forEach((key, value) -> { rpcSubscriptions.forEach((key, value) -> {
@ -202,7 +209,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
syncSessionSet.add(key); syncSessionSet.add(key);
} }
}); });
log.trace("46) Rpc syncSessionSet [{}] subscription after sent [{}]", syncSessionSet, rpcSubscriptions); log.trace("Rpc syncSessionSet [{}] subscription after sent [{}]", syncSessionSet, rpcSubscriptions);
syncSessionSet.forEach(rpcSubscriptions::remove); syncSessionSet.forEach(rpcSubscriptions::remove);
} }
@ -225,6 +232,10 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
} }
} }
private boolean isSendNewRpcAvailable() {
return !rpcSequential || toDeviceRpcPendingMap.values().stream().filter(md -> !md.isDelivered()).findAny().isEmpty();
}
private Rpc createRpc(ToDeviceRpcRequest request, RpcStatus status) { private Rpc createRpc(ToDeviceRpcRequest request, RpcStatus status) {
Rpc rpc = new Rpc(new RpcId(request.getId())); Rpc rpc = new Rpc(new RpcId(request.getId()));
rpc.setCreatedTime(System.currentTimeMillis()); rpc.setCreatedTime(System.currentTimeMillis());
@ -233,6 +244,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
rpc.setExpirationTime(request.getExpirationTime()); rpc.setExpirationTime(request.getExpirationTime());
rpc.setRequest(JacksonUtil.valueToTree(request)); rpc.setRequest(JacksonUtil.valueToTree(request));
rpc.setStatus(status); rpc.setStatus(status);
rpc.setAdditionalInfo(JacksonUtil.valueToTree(request.getAdditionalInfo()));
return systemContext.getTbRpcService().save(tenantId, rpc); return systemContext.getTbRpcService().save(tenantId, rpc);
} }
@ -261,6 +273,31 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
} }
} }
void processRemoveRpc(TbActorCtx context, RemoveRpcActorMsg msg) {
log.debug("[{}] Processing remove rpc command", msg.getRequestId());
Map.Entry<Integer, ToDeviceRpcRequestMetadata> entry = null;
for (Map.Entry<Integer, ToDeviceRpcRequestMetadata> e : toDeviceRpcPendingMap.entrySet()) {
if (e.getValue().getMsg().getMsg().getId().equals(msg.getRequestId())) {
entry = e;
break;
}
}
if (entry != null) {
if (entry.getValue().isDelivered()) {
toDeviceRpcPendingMap.remove(entry.getKey());
} else {
Optional<Map.Entry<Integer, ToDeviceRpcRequestMetadata>> firstRpc = getFirstRpc();
if (firstRpc.isPresent() && entry.getKey().equals(firstRpc.get().getKey())) {
toDeviceRpcPendingMap.remove(entry.getKey());
sendNextPendingRequest(context);
} else {
toDeviceRpcPendingMap.remove(entry.getKey());
}
}
}
}
private void registerPendingRpcRequest(TbActorCtx context, ToDeviceRpcRequestActorMsg msg, boolean sent, ToDeviceRpcRequestMsg rpcRequest, long timeout) { private void registerPendingRpcRequest(TbActorCtx context, ToDeviceRpcRequestActorMsg msg, boolean sent, ToDeviceRpcRequestMsg rpcRequest, long timeout) {
toDeviceRpcPendingMap.put(rpcRequest.getRequestId(), new ToDeviceRpcRequestMetadata(msg, sent)); toDeviceRpcPendingMap.put(rpcRequest.getRequestId(), new ToDeviceRpcRequestMetadata(msg, sent));
DeviceActorServerSideRpcTimeoutMsg timeoutMsg = new DeviceActorServerSideRpcTimeoutMsg(rpcRequest.getRequestId(), timeout); DeviceActorServerSideRpcTimeoutMsg timeoutMsg = new DeviceActorServerSideRpcTimeoutMsg(rpcRequest.getRequestId(), timeout);
@ -272,14 +309,17 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
if (requestMd != null) { if (requestMd != null) {
log.debug("[{}] RPC request [{}] timeout detected!", deviceId, msg.getId()); log.debug("[{}] RPC request [{}] timeout detected!", deviceId, msg.getId());
if (requestMd.getMsg().getMsg().isPersisted()) { if (requestMd.getMsg().getMsg().isPersisted()) {
systemContext.getTbRpcService().save(tenantId, new RpcId(requestMd.getMsg().getMsg().getId()), RpcStatus.TIMEOUT, null); systemContext.getTbRpcService().save(tenantId, new RpcId(requestMd.getMsg().getMsg().getId()), RpcStatus.EXPIRED, null);
} }
systemContext.getTbCoreDeviceRpcService().processRpcResponseFromDeviceActor(new FromDeviceRpcResponse(requestMd.getMsg().getMsg().getId(), systemContext.getTbCoreDeviceRpcService().processRpcResponseFromDeviceActor(new FromDeviceRpcResponse(requestMd.getMsg().getMsg().getId(),
null, requestMd.isSent() ? RpcError.TIMEOUT : RpcError.NO_ACTIVE_CONNECTION)); null, requestMd.isSent() ? RpcError.TIMEOUT : RpcError.NO_ACTIVE_CONNECTION));
if (!requestMd.isDelivered()) {
sendNextPendingRequest(context);
}
} }
} }
private void sendPendingRequests(TbActorCtx context, UUID sessionId, SessionInfoProto sessionInfo) { private void sendPendingRequests(TbActorCtx context, UUID sessionId, String nodeId) {
SessionType sessionType = getSessionType(sessionId); SessionType sessionType = getSessionType(sessionId);
if (!toDeviceRpcPendingMap.isEmpty()) { if (!toDeviceRpcPendingMap.isEmpty()) {
log.debug("[{}] Pushing {} pending RPC messages to new async session [{}]", deviceId, toDeviceRpcPendingMap.size(), sessionId); log.debug("[{}] Pushing {} pending RPC messages to new async session [{}]", deviceId, toDeviceRpcPendingMap.size(), sessionId);
@ -291,20 +331,33 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
log.debug("[{}] No pending RPC messages for new async session [{}]", deviceId, sessionId); log.debug("[{}] No pending RPC messages for new async session [{}]", deviceId, sessionId);
} }
Set<Integer> sentOneWayIds = new HashSet<>(); Set<Integer> sentOneWayIds = new HashSet<>();
if (sessionType == SessionType.ASYNC) {
toDeviceRpcPendingMap.entrySet().forEach(processPendingRpc(context, sessionId, sessionInfo.getNodeId(), sentOneWayIds)); if (rpcSequential) {
getFirstRpc().ifPresent(processPendingRpc(context, sessionId, nodeId, sentOneWayIds));
} else if (sessionType == SessionType.ASYNC) {
toDeviceRpcPendingMap.entrySet().forEach(processPendingRpc(context, sessionId, nodeId, sentOneWayIds));
} else { } else {
toDeviceRpcPendingMap.entrySet().stream().findFirst().ifPresent(processPendingRpc(context, sessionId, sessionInfo.getNodeId(), sentOneWayIds)); toDeviceRpcPendingMap.entrySet().stream().findFirst().ifPresent(processPendingRpc(context, sessionId, nodeId, sentOneWayIds));
} }
sentOneWayIds.stream().filter(id -> !toDeviceRpcPendingMap.get(id).getMsg().getMsg().isPersisted()).forEach(toDeviceRpcPendingMap::remove); sentOneWayIds.stream().filter(id -> !toDeviceRpcPendingMap.get(id).getMsg().getMsg().isPersisted()).forEach(toDeviceRpcPendingMap::remove);
} }
private Optional<Map.Entry<Integer, ToDeviceRpcRequestMetadata>> getFirstRpc() {
return toDeviceRpcPendingMap.entrySet().stream().filter(e -> !e.getValue().isDelivered()).findFirst();
}
private void sendNextPendingRequest(TbActorCtx context) {
if (rpcSequential) {
rpcSubscriptions.forEach((id, s) -> sendPendingRequests(context, id, s.getNodeId()));
}
}
private Consumer<Map.Entry<Integer, ToDeviceRpcRequestMetadata>> processPendingRpc(TbActorCtx context, UUID sessionId, String nodeId, Set<Integer> sentOneWayIds) { private Consumer<Map.Entry<Integer, ToDeviceRpcRequestMetadata>> processPendingRpc(TbActorCtx context, UUID sessionId, String nodeId, Set<Integer> sentOneWayIds) {
return entry -> { return entry -> {
ToDeviceRpcRequest request = entry.getValue().getMsg().getMsg(); ToDeviceRpcRequest request = entry.getValue().getMsg().getMsg();
ToDeviceRpcRequestBody body = request.getBody(); ToDeviceRpcRequestBody body = request.getBody();
if (request.isOneway()) { if (request.isOneway() && !rpcSequential) {
sentOneWayIds.add(entry.getKey()); sentOneWayIds.add(entry.getKey());
systemContext.getTbCoreDeviceRpcService().processRpcResponseFromDeviceActor(new FromDeviceRpcResponse(request.getId(), null, null)); systemContext.getTbCoreDeviceRpcService().processRpcResponseFromDeviceActor(new FromDeviceRpcResponse(request.getId(), null, null));
} }
@ -318,7 +371,6 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
.setOneway(request.isOneway()) .setOneway(request.isOneway())
.setPersisted(request.isPersisted()) .setPersisted(request.isPersisted())
.build(); .build();
sendToTransport(rpcRequest, sessionId, nodeId); sendToTransport(rpcRequest, sessionId, nodeId);
}; };
} }
@ -338,7 +390,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
processSubscriptionCommands(context, sessionInfo, msg.getSubscribeToRPC()); processSubscriptionCommands(context, sessionInfo, msg.getSubscribeToRPC());
} }
if (msg.hasSendPendingRPC()) { if (msg.hasSendPendingRPC()) {
sendPendingRequests(context, getSessionId(sessionInfo), sessionInfo); sendPendingRequests(context, getSessionId(sessionInfo), sessionInfo.getNodeId());
} }
if (msg.hasGetAttributes()) { if (msg.hasGetAttributes()) {
handleGetAttributesRequest(context, sessionInfo, msg.getGetAttributes()); handleGetAttributesRequest(context, sessionInfo, msg.getGetAttributes());
@ -352,23 +404,40 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
if (msg.hasClaimDevice()) { if (msg.hasClaimDevice()) {
handleClaimDeviceMsg(context, sessionInfo, msg.getClaimDevice()); handleClaimDeviceMsg(context, sessionInfo, msg.getClaimDevice());
} }
if (msg.hasPersistedRpcResponseMsg()) { if (msg.hasRpcResponseStatusMsg()) {
processPersistedRpcResponses(context, sessionInfo, msg.getPersistedRpcResponseMsg()); processRpcResponseStatus(context, sessionInfo, msg.getRpcResponseStatusMsg());
}
if (msg.hasUplinkNotificationMsg()) {
processUplinkNotificationMsg(context, sessionInfo, msg.getUplinkNotificationMsg());
} }
callback.onSuccess(); callback.onSuccess();
} }
private void processUplinkNotificationMsg(TbActorCtx context, SessionInfoProto sessionInfo, TransportProtos.UplinkNotificationMsg uplinkNotificationMsg) {
String nodeId = sessionInfo.getNodeId();
sessions.entrySet().stream()
.filter(kv -> kv.getValue().getSessionInfo().getNodeId().equals(nodeId) && (kv.getValue().isSubscribedToAttributes() || kv.getValue().isSubscribedToRPC()))
.forEach(kv -> {
ToTransportMsg msg = ToTransportMsg.newBuilder()
.setSessionIdMSB(kv.getKey().getMostSignificantBits())
.setSessionIdLSB(kv.getKey().getLeastSignificantBits())
.setUplinkNotificationMsg(uplinkNotificationMsg)
.build();
systemContext.getTbCoreToTransportService().process(kv.getValue().getSessionInfo().getNodeId(), msg);
});
}
private void handleClaimDeviceMsg(TbActorCtx context, SessionInfoProto sessionInfo, ClaimDeviceMsg msg) { private void handleClaimDeviceMsg(TbActorCtx context, SessionInfoProto sessionInfo, ClaimDeviceMsg msg) {
DeviceId deviceId = new DeviceId(new UUID(msg.getDeviceIdMSB(), msg.getDeviceIdLSB())); DeviceId deviceId = new DeviceId(new UUID(msg.getDeviceIdMSB(), msg.getDeviceIdLSB()));
systemContext.getClaimDevicesService().registerClaimingInfo(tenantId, deviceId, msg.getSecretKey(), msg.getDurationMs()); systemContext.getClaimDevicesService().registerClaimingInfo(tenantId, deviceId, msg.getSecretKey(), msg.getDurationMs());
} }
private void reportSessionOpen() { private void reportSessionOpen() {
systemContext.getDeviceStateService().onDeviceConnect(deviceId); systemContext.getDeviceStateService().onDeviceConnect(tenantId, deviceId);
} }
private void reportSessionClose() { private void reportSessionClose() {
systemContext.getDeviceStateService().onDeviceDisconnect(deviceId); systemContext.getDeviceStateService().onDeviceDisconnect(tenantId, deviceId);
} }
private void handleGetAttributesRequest(TbActorCtx context, SessionInfoProto sessionInfo, GetAttributeRequestMsg request) { private void handleGetAttributesRequest(TbActorCtx context, SessionInfoProto sessionInfo, GetAttributeRequestMsg request) {
@ -495,19 +564,67 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(responseMsg.getRequestId()); ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(responseMsg.getRequestId());
boolean success = requestMd != null; boolean success = requestMd != null;
if (success) { if (success) {
systemContext.getTbCoreDeviceRpcService().processRpcResponseFromDeviceActor(new FromDeviceRpcResponse(requestMd.getMsg().getMsg().getId(), boolean hasError = StringUtils.isNotEmpty(responseMsg.getError());
responseMsg.getPayload(), null)); try {
if (requestMd.getMsg().getMsg().isPersisted()) { String payload = hasError ? responseMsg.getError() : responseMsg.getPayload();
systemContext.getTbRpcService().save(tenantId, new RpcId(requestMd.getMsg().getMsg().getId()), RpcStatus.SUCCESSFUL, JacksonUtil.toJsonNode(responseMsg.getPayload())); systemContext.getTbCoreDeviceRpcService().processRpcResponseFromDeviceActor(
new FromDeviceRpcResponse(requestMd.getMsg().getMsg().getId(),
payload, null));
if (requestMd.getMsg().getMsg().isPersisted()) {
RpcStatus status = hasError ? RpcStatus.FAILED : RpcStatus.SUCCESSFUL;
JsonNode response;
try {
response = JacksonUtil.toJsonNode(payload);
} catch (IllegalArgumentException e) {
response = JacksonUtil.newObjectNode().put("error", payload);
}
systemContext.getTbRpcService().save(tenantId, new RpcId(requestMd.getMsg().getMsg().getId()), status, response);
}
} finally {
if (hasError) {
sendNextPendingRequest(context);
}
} }
} else { } else {
log.debug("[{}] Rpc command response [{}] is stale!", deviceId, responseMsg.getRequestId()); log.debug("[{}] Rpc command response [{}] is stale!", deviceId, responseMsg.getRequestId());
} }
} }
private void processPersistedRpcResponses(TbActorCtx context, SessionInfoProto sessionInfo, ToDevicePersistedRpcResponseMsg responseMsg) { private void processRpcResponseStatus(TbActorCtx context, SessionInfoProto sessionInfo, ToDeviceRpcResponseStatusMsg responseMsg) {
UUID rpcId = new UUID(responseMsg.getRequestIdMSB(), responseMsg.getRequestIdLSB()); UUID rpcId = new UUID(responseMsg.getRequestIdMSB(), responseMsg.getRequestIdLSB());
systemContext.getTbRpcService().save(tenantId, new RpcId(rpcId), RpcStatus.valueOf(responseMsg.getStatus()), null); RpcStatus status = RpcStatus.valueOf(responseMsg.getStatus());
ToDeviceRpcRequestMetadata md = toDeviceRpcPendingMap.get(responseMsg.getRequestId());
if (md != null) {
if (status.equals(RpcStatus.DELIVERED)) {
if (md.getMsg().getMsg().isOneway()) {
toDeviceRpcPendingMap.remove(responseMsg.getRequestId());
if (rpcSequential) {
systemContext.getTbCoreDeviceRpcService().processRpcResponseFromDeviceActor(new FromDeviceRpcResponse(rpcId, null, null));
}
} else {
md.setDelivered(true);
}
} else if (status.equals(RpcStatus.TIMEOUT)) {
Integer maxRpcRetries = md.getMsg().getMsg().getRetries();
maxRpcRetries = maxRpcRetries == null ? systemContext.getMaxRpcRetries() : Math.min(maxRpcRetries, systemContext.getMaxRpcRetries());
if (maxRpcRetries <= md.getRetries()) {
toDeviceRpcPendingMap.remove(responseMsg.getRequestId());
status = RpcStatus.FAILED;
} else {
md.setRetries(md.getRetries() + 1);
}
}
if (md.getMsg().getMsg().isPersisted()) {
systemContext.getTbRpcService().save(tenantId, new RpcId(rpcId), status, null);
}
if (status != RpcStatus.SENT) {
sendNextPendingRequest(context);
}
} else {
log.info("[{}][{}] Rpc has already removed from pending map.", deviceId, rpcId);
}
} }
private void processSubscriptionCommands(TbActorCtx context, SessionInfoProto sessionInfo, SubscribeToAttributeUpdatesMsg subscribeCmd) { private void processSubscriptionCommands(TbActorCtx context, SessionInfoProto sessionInfo, SubscribeToAttributeUpdatesMsg subscribeCmd) {
@ -544,7 +661,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
sessionMD.setSubscribedToRPC(true); sessionMD.setSubscribedToRPC(true);
log.debug("[{}] Registering rpc subscription for session [{}]", deviceId, sessionId); log.debug("[{}] Registering rpc subscription for session [{}]", deviceId, sessionId);
rpcSubscriptions.put(sessionId, sessionMD.getSessionInfo()); rpcSubscriptions.put(sessionId, sessionMD.getSessionInfo());
sendPendingRequests(context, sessionId, sessionInfo); sendPendingRequests(context, sessionId, sessionInfo.getNodeId());
dumpSessions(); dumpSessions();
} }
} }
@ -557,13 +674,13 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
log.debug("[{}] Received duplicate session open event [{}]", deviceId, sessionId); log.debug("[{}] Received duplicate session open event [{}]", deviceId, sessionId);
return; return;
} }
log.info("[{}] Processing new session [{}]. Current sessions size {}", deviceId, sessionId, sessions.size()); log.debug("[{}] Processing new session [{}]. Current sessions size {}", deviceId, sessionId, sessions.size());
sessions.put(sessionId, new SessionInfoMetaData(new SessionInfo(SessionType.ASYNC, sessionInfo.getNodeId()))); sessions.put(sessionId, new SessionInfoMetaData(new SessionInfo(SessionType.ASYNC, sessionInfo.getNodeId())));
if (sessions.size() == 1) { if (sessions.size() == 1) {
reportSessionOpen(); reportSessionOpen();
} }
systemContext.getDeviceStateService().onDeviceActivity(deviceId, System.currentTimeMillis()); systemContext.getDeviceStateService().onDeviceActivity(tenantId, deviceId, System.currentTimeMillis());
dumpSessions(); dumpSessions();
} else if (msg.getEvent() == SessionEvent.CLOSED) { } else if (msg.getEvent() == SessionEvent.CLOSED) {
log.debug("[{}] Canceling subscriptions for closed session [{}]", deviceId, sessionId); log.debug("[{}] Canceling subscriptions for closed session [{}]", deviceId, sessionId);
@ -581,25 +698,26 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
UUID sessionId = getSessionId(sessionInfoProto); UUID sessionId = getSessionId(sessionInfoProto);
Objects.requireNonNull(sessionId); Objects.requireNonNull(sessionId);
SessionInfoMetaData sessionMD = sessions.computeIfAbsent(sessionId, SessionInfoMetaData sessionMD = sessions.get(sessionId);
id -> new SessionInfoMetaData(new SessionInfo(SessionType.ASYNC, sessionInfoProto.getNodeId()), subscriptionInfo.getLastActivityTime())); if (sessionMD != null) {
sessionMD.setLastActivityTime(subscriptionInfo.getLastActivityTime());
sessionMD.setLastActivityTime(subscriptionInfo.getLastActivityTime()); sessionMD.setSubscribedToAttributes(subscriptionInfo.getAttributeSubscription());
sessionMD.setSubscribedToAttributes(subscriptionInfo.getAttributeSubscription()); sessionMD.setSubscribedToRPC(subscriptionInfo.getRpcSubscription());
sessionMD.setSubscribedToRPC(subscriptionInfo.getRpcSubscription()); if (subscriptionInfo.getAttributeSubscription()) {
if (subscriptionInfo.getAttributeSubscription()) { attributeSubscriptions.putIfAbsent(sessionId, sessionMD.getSessionInfo());
attributeSubscriptions.putIfAbsent(sessionId, sessionMD.getSessionInfo()); }
if (subscriptionInfo.getRpcSubscription()) {
rpcSubscriptions.putIfAbsent(sessionId, sessionMD.getSessionInfo());
}
} }
if (subscriptionInfo.getRpcSubscription()) { systemContext.getDeviceStateService().onDeviceActivity(tenantId, deviceId, subscriptionInfo.getLastActivityTime());
rpcSubscriptions.putIfAbsent(sessionId, sessionMD.getSessionInfo()); if (sessionMD != null) {
dumpSessions();
} }
systemContext.getDeviceStateService().onDeviceActivity(deviceId, subscriptionInfo.getLastActivityTime());
dumpSessions();
} }
void processCredentialsUpdate(TbActorMsg msg) { void processCredentialsUpdate(TbActorMsg msg) {
if (((DeviceCredentialsUpdateNotificationMsg) msg).getDeviceCredentials().getCredentialsType() == DeviceCredentialsType.LWM2M_CREDENTIALS) { if (((DeviceCredentialsUpdateNotificationMsg) msg).getDeviceCredentials().getCredentialsType() == DeviceCredentialsType.LWM2M_CREDENTIALS) {
log.info("1) LwM2Mtype: ");
sessions.forEach((k, v) -> { sessions.forEach((k, v) -> {
notifyTransportAboutProfileUpdate(k, v, ((DeviceCredentialsUpdateNotificationMsg) msg).getDeviceCredentials()); notifyTransportAboutProfileUpdate(k, v, ((DeviceCredentialsUpdateNotificationMsg) msg).getDeviceCredentials());
}); });
@ -616,7 +734,6 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
notifyTransportAboutClosedSession(sessionId, sessionMd, "max concurrent sessions limit reached per device!"); notifyTransportAboutClosedSession(sessionId, sessionMd, "max concurrent sessions limit reached per device!");
} }
private void notifyTransportAboutClosedSession(UUID sessionId, SessionInfoMetaData sessionMd, String message) { private void notifyTransportAboutClosedSession(UUID sessionId, SessionInfoMetaData sessionMd, String message) {
SessionCloseNotificationProto sessionCloseNotificationProto = SessionCloseNotificationProto SessionCloseNotificationProto sessionCloseNotificationProto = SessionCloseNotificationProto
.newBuilder() .newBuilder()
@ -630,7 +747,6 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
} }
void notifyTransportAboutProfileUpdate(UUID sessionId, SessionInfoMetaData sessionMd, DeviceCredentials deviceCredentials) { void notifyTransportAboutProfileUpdate(UUID sessionId, SessionInfoMetaData sessionMd, DeviceCredentials deviceCredentials) {
log.info("2) LwM2Mtype: ");
ToTransportUpdateCredentialsProto.Builder notification = ToTransportUpdateCredentialsProto.newBuilder(); ToTransportUpdateCredentialsProto.Builder notification = ToTransportUpdateCredentialsProto.newBuilder();
notification.addCredentialsId(deviceCredentials.getCredentialsId()); notification.addCredentialsId(deviceCredentials.getCredentialsId());
notification.addCredentialsValue(deviceCredentials.getCredentialsValue()); notification.addCredentialsValue(deviceCredentials.getCredentialsValue());
@ -703,18 +819,8 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
edgeEvent.setBody(body); edgeEvent.setBody(body);
edgeEvent.setEdgeId(edgeId); edgeEvent.setEdgeId(edgeId);
ListenableFuture<EdgeEvent> future = systemContext.getEdgeEventService().saveAsync(edgeEvent); systemContext.getEdgeEventService().save(edgeEvent);
Futures.addCallback(future, new FutureCallback<EdgeEvent>() { systemContext.getClusterService().onEdgeEventUpdate(tenantId, edgeId);
@Override
public void onSuccess(EdgeEvent result) {
systemContext.getClusterService().onEdgeEventUpdate(tenantId, edgeId);
}
@Override
public void onFailure(Throwable t) {
log.warn("[{}] Can't save edge event [{}] for edge [{}]", tenantId.getId(), edgeEvent, edgeId.getId(), t);
}
}, systemContext.getDbCallbackExecutor());
} }
private List<TsKvProto> toTsKvProtos(@Nullable List<AttributeKvEntry> result) { private List<TsKvProto> toTsKvProtos(@Nullable List<AttributeKvEntry> result) {
@ -825,7 +931,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
void init(TbActorCtx ctx) { void init(TbActorCtx ctx) {
schedulePeriodicMsgWithDelay(ctx, SessionTimeoutCheckMsg.instance(), systemContext.getSessionReportTimeout(), systemContext.getSessionReportTimeout()); schedulePeriodicMsgWithDelay(ctx, SessionTimeoutCheckMsg.instance(), systemContext.getSessionReportTimeout(), systemContext.getSessionReportTimeout());
PageLink pageLink = new PageLink(1024); PageLink pageLink = new PageLink(1024, 0, null, new SortOrder("createdTime"));
PageData<Rpc> pageData; PageData<Rpc> pageData;
do { do {
pageData = systemContext.getTbRpcService().findAllByDeviceIdAndStatus(tenantId, deviceId, RpcStatus.QUEUED, pageLink); pageData = systemContext.getTbRpcService().findAllByDeviceIdAndStatus(tenantId, deviceId, RpcStatus.QUEUED, pageLink);
@ -833,7 +939,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
ToDeviceRpcRequest msg = JacksonUtil.convertValue(rpc.getRequest(), ToDeviceRpcRequest.class); ToDeviceRpcRequest msg = JacksonUtil.convertValue(rpc.getRequest(), ToDeviceRpcRequest.class);
long timeout = rpc.getExpirationTime() - System.currentTimeMillis(); long timeout = rpc.getExpirationTime() - System.currentTimeMillis();
if (timeout <= 0) { if (timeout <= 0) {
rpc.setStatus(RpcStatus.TIMEOUT); rpc.setStatus(RpcStatus.EXPIRED);
systemContext.getTbRpcService().save(tenantId, rpc); systemContext.getTbRpcService().save(tenantId, rpc);
} else { } else {
registerPendingRpcRequest(ctx, new ToDeviceRpcRequestActorMsg(systemContext.getServiceId(), msg), false, creteToDeviceRpcRequestMsg(msg), timeout); registerPendingRpcRequest(ctx, new ToDeviceRpcRequestActorMsg(systemContext.getServiceId(), msg), false, creteToDeviceRpcRequestMsg(msg), timeout);

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

@ -25,4 +25,6 @@ import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg;
public class ToDeviceRpcRequestMetadata { public class ToDeviceRpcRequestMetadata {
private final ToDeviceRpcRequestActorMsg msg; private final ToDeviceRpcRequestActorMsg msg;
private final boolean sent; private final boolean sent;
private int retries;
private boolean delivered;
} }

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

@ -33,6 +33,7 @@ import org.thingsboard.rule.engine.api.TbRelationTypes;
import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; import org.thingsboard.rule.engine.api.sms.SmsSenderFactory;
import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.TbActorRef; import org.thingsboard.server.actors.TbActorRef;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
@ -453,6 +454,11 @@ class DefaultTbContext implements TbContext {
return mainCtx.getDeviceService(); return mainCtx.getDeviceService();
} }
@Override
public TbClusterService getClusterService() {
return mainCtx.getClusterService();
}
@Override @Override
public DashboardService getDashboardService() { public DashboardService getDashboardService() {
return mainCtx.getDashboardService(); return mainCtx.getDashboardService();
@ -519,8 +525,8 @@ class DefaultTbContext implements TbContext {
} }
@Override @Override
public MailService getMailService() { public MailService getMailService(boolean isSystem) {
if (mainCtx.isAllowSystemMailService()) { if (!isSystem || mainCtx.isAllowSystemMailService()) {
return mainCtx.getMailService(); return mainCtx.getMailService();
} else { } else {
throw new RuntimeException("Access to System Mail Service is forbidden!"); throw new RuntimeException("Access to System Mail Service is forbidden!");

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

@ -49,7 +49,7 @@ import org.thingsboard.server.queue.TbQueueCallback;
import org.thingsboard.server.queue.common.MultipleTbQueueTbMsgCallbackWrapper; import org.thingsboard.server.queue.common.MultipleTbQueueTbMsgCallbackWrapper;
import org.thingsboard.server.queue.common.TbQueueTbMsgCallbackWrapper; import org.thingsboard.server.queue.common.TbQueueTbMsgCallbackWrapper;
import org.thingsboard.server.queue.usagestats.TbApiUsageClient; import org.thingsboard.server.queue.usagestats.TbApiUsageClient;
import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;

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

@ -165,6 +165,7 @@ public class TenantActor extends RuleChainManagerActor {
case DEVICE_RPC_REQUEST_TO_DEVICE_ACTOR_MSG: case DEVICE_RPC_REQUEST_TO_DEVICE_ACTOR_MSG:
case DEVICE_RPC_RESPONSE_TO_DEVICE_ACTOR_MSG: case DEVICE_RPC_RESPONSE_TO_DEVICE_ACTOR_MSG:
case SERVER_RPC_RESPONSE_TO_DEVICE_ACTOR_MSG: case SERVER_RPC_RESPONSE_TO_DEVICE_ACTOR_MSG:
case REMOVE_RPC_TO_DEVICE_ACTOR_MSG:
onToDeviceActorMsg((DeviceAwareMsg) msg, true); onToDeviceActorMsg((DeviceAwareMsg) msg, true);
break; break;
case RULE_CHAIN_TO_RULE_CHAIN_MSG: case RULE_CHAIN_TO_RULE_CHAIN_MSG:

126
application/src/main/java/org/thingsboard/server/controller/RpcController.java → application/src/main/java/org/thingsboard/server/controller/AbstractRpcController.java

@ -16,41 +16,28 @@
package org.thingsboard.server.controller; package org.thingsboard.server.controller;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureCallback;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
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.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult; import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.rule.engine.api.RpcError; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.rpc.RpcError;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.ActionType;
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.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.RpcId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UUIDBased; import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.rpc.Rpc;
import org.thingsboard.server.common.data.rpc.RpcStatus;
import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody; import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.rpc.FromDeviceRpcResponse; import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse;
import org.thingsboard.server.service.rpc.LocalRequestMetaData; import org.thingsboard.server.service.rpc.LocalRequestMetaData;
import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService; import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService;
import org.thingsboard.server.service.security.AccessValidator; import org.thingsboard.server.service.security.AccessValidator;
@ -59,105 +46,42 @@ import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.telemetry.exception.ToErrorResponseEntity; import org.thingsboard.server.service.telemetry.exception.ToErrorResponseEntity;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.io.IOException;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
/** /**
* Created by ashvayka on 22.03.18. * Created by ashvayka on 22.03.18.
*/ */
@RestController
@TbCoreComponent @TbCoreComponent
@RequestMapping(TbUrlConstants.RPC_URL_PREFIX)
@Slf4j @Slf4j
public class RpcController extends BaseController { public abstract class AbstractRpcController extends BaseController {
protected final ObjectMapper jsonMapper = new ObjectMapper();
@Autowired @Autowired
private TbCoreDeviceRpcService deviceRpcService; protected TbCoreDeviceRpcService deviceRpcService;
@Autowired @Autowired
private AccessValidator accessValidator; private AccessValidator accessValidator;
@Value("${server.rest.server_side_rpc.min_timeout:5000}") @Value("${server.rest.server_side_rpc.min_timeout:5000}")
private long minTimeout; protected long minTimeout;
@Value("${server.rest.server_side_rpc.default_timeout:10000}") @Value("${server.rest.server_side_rpc.default_timeout:10000}")
private long defaultTimeout; protected long defaultTimeout;
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/oneway/{deviceId}", method = RequestMethod.POST)
@ResponseBody
public DeferredResult<ResponseEntity> handleOneWayDeviceRPCRequest(@PathVariable("deviceId") String deviceIdStr, @RequestBody String requestBody) throws ThingsboardException {
return handleDeviceRPCRequest(true, new DeviceId(UUID.fromString(deviceIdStr)), requestBody);
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/twoway/{deviceId}", method = RequestMethod.POST)
@ResponseBody
public DeferredResult<ResponseEntity> handleTwoWayDeviceRPCRequest(@PathVariable("deviceId") String deviceIdStr, @RequestBody String requestBody) throws ThingsboardException {
return handleDeviceRPCRequest(false, new DeviceId(UUID.fromString(deviceIdStr)), requestBody);
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/persistent/{rpcId}", method = RequestMethod.GET)
@ResponseBody
public Rpc getPersistedRpc(@PathVariable("rpcId") String strRpc) throws ThingsboardException {
checkParameter("RpcId", strRpc);
try {
RpcId rpcId = new RpcId(UUID.fromString(strRpc));
return checkRpcId(rpcId, Operation.READ);
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/persistent/device/{deviceId}", method = RequestMethod.GET)
@ResponseBody
public PageData<Rpc> getPersistedRpcByDevice(@PathVariable("deviceId") String strDeviceId,
@RequestParam int pageSize,
@RequestParam int page,
@RequestParam RpcStatus rpcStatus,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
checkParameter("DeviceId", strDeviceId);
try {
TenantId tenantId = getCurrentUser().getTenantId();
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
DeviceId deviceId = new DeviceId(UUID.fromString(strDeviceId));
return checkNotNull(rpcService.findAllByDeviceIdAndStatus(tenantId, deviceId, rpcStatus, pageLink));
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/persistent/{rpcId}", method = RequestMethod.DELETE)
@ResponseBody
public void deleteResource(@PathVariable("rpcId") String strRpc) throws ThingsboardException {
checkParameter("RpcId", strRpc);
try {
rpcService.deleteRpc(getTenantId(), new RpcId(UUID.fromString(strRpc)));
} catch (Exception e) {
throw handleException(e);
}
}
private DeferredResult<ResponseEntity> handleDeviceRPCRequest(boolean oneWay, DeviceId deviceId, String requestBody) throws ThingsboardException { protected DeferredResult<ResponseEntity> handleDeviceRPCRequest(boolean oneWay, DeviceId deviceId, String requestBody, HttpStatus timeoutStatus, HttpStatus noActiveConnectionStatus) throws ThingsboardException {
try { try {
JsonNode rpcRequestBody = jsonMapper.readTree(requestBody); JsonNode rpcRequestBody = JacksonUtil.toJsonNode(requestBody);
ToDeviceRpcRequestBody body = new ToDeviceRpcRequestBody(rpcRequestBody.get("method").asText(), jsonMapper.writeValueAsString(rpcRequestBody.get("params"))); ToDeviceRpcRequestBody body = new ToDeviceRpcRequestBody(rpcRequestBody.get("method").asText(), JacksonUtil.toString(rpcRequestBody.get("params")));
SecurityUser currentUser = getCurrentUser(); SecurityUser currentUser = getCurrentUser();
TenantId tenantId = currentUser.getTenantId(); TenantId tenantId = currentUser.getTenantId();
final DeferredResult<ResponseEntity> response = new DeferredResult<>(); final DeferredResult<ResponseEntity> response = new DeferredResult<>();
long timeout = rpcRequestBody.has("timeout") ? rpcRequestBody.get("timeout").asLong() : defaultTimeout; long timeout = rpcRequestBody.has(DataConstants.TIMEOUT) ? rpcRequestBody.get(DataConstants.TIMEOUT).asLong() : defaultTimeout;
long expTime = System.currentTimeMillis() + Math.max(minTimeout, timeout); long expTime = rpcRequestBody.has(DataConstants.EXPIRATION_TIME) ? rpcRequestBody.get(DataConstants.EXPIRATION_TIME).asLong() : System.currentTimeMillis() + Math.max(minTimeout, timeout);
UUID rpcRequestUUID = rpcRequestBody.has("requestUUID") ? UUID.fromString(rpcRequestBody.get("requestUUID").asText()) : UUID.randomUUID(); UUID rpcRequestUUID = rpcRequestBody.has("requestUUID") ? UUID.fromString(rpcRequestBody.get("requestUUID").asText()) : UUID.randomUUID();
boolean persisted = rpcRequestBody.has(DataConstants.PERSISTENT) && rpcRequestBody.get(DataConstants.PERSISTENT).asBoolean(); boolean persisted = rpcRequestBody.has(DataConstants.PERSISTENT) && rpcRequestBody.get(DataConstants.PERSISTENT).asBoolean();
accessValidator.validate(currentUser, Operation.RPC_CALL, deviceId, new HttpValidationCallback(response, new FutureCallback<DeferredResult<ResponseEntity>>() { String additionalInfo = JacksonUtil.toString(rpcRequestBody.get(DataConstants.ADDITIONAL_INFO));
Integer retries = rpcRequestBody.has(DataConstants.RETRIES) ? rpcRequestBody.get(DataConstants.RETRIES).asInt() : null;
accessValidator.validate(currentUser, Operation.RPC_CALL, deviceId, new HttpValidationCallback(response, new FutureCallback<>() {
@Override @Override
public void onSuccess(@Nullable DeferredResult<ResponseEntity> result) { public void onSuccess(@Nullable DeferredResult<ResponseEntity> result) {
ToDeviceRpcRequest rpcRequest = new ToDeviceRpcRequest(rpcRequestUUID, ToDeviceRpcRequest rpcRequest = new ToDeviceRpcRequest(rpcRequestUUID,
@ -166,9 +90,11 @@ public class RpcController extends BaseController {
oneWay, oneWay,
expTime, expTime,
body, body,
persisted persisted,
retries,
additionalInfo
); );
deviceRpcService.processRestApiRpcRequest(rpcRequest, fromDeviceRpcResponse -> reply(new LocalRequestMetaData(rpcRequest, currentUser, result), fromDeviceRpcResponse), currentUser); deviceRpcService.processRestApiRpcRequest(rpcRequest, fromDeviceRpcResponse -> reply(new LocalRequestMetaData(rpcRequest, currentUser, result), fromDeviceRpcResponse, timeoutStatus, noActiveConnectionStatus), currentUser);
} }
@Override @Override
@ -184,12 +110,12 @@ public class RpcController extends BaseController {
} }
})); }));
return response; return response;
} catch (IOException ioe) { } catch (IllegalArgumentException ioe) {
throw new ThingsboardException("Invalid request body", ioe, ThingsboardErrorCode.BAD_REQUEST_PARAMS); throw new ThingsboardException("Invalid request body", ioe, ThingsboardErrorCode.BAD_REQUEST_PARAMS);
} }
} }
public void reply(LocalRequestMetaData rpcRequest, FromDeviceRpcResponse response) { public void reply(LocalRequestMetaData rpcRequest, FromDeviceRpcResponse response, HttpStatus timeoutStatus, HttpStatus noActiveConnectionStatus) {
Optional<RpcError> rpcError = response.getError(); Optional<RpcError> rpcError = response.getError();
DeferredResult<ResponseEntity> responseWriter = rpcRequest.getResponseWriter(); DeferredResult<ResponseEntity> responseWriter = rpcRequest.getResponseWriter();
if (rpcError.isPresent()) { if (rpcError.isPresent()) {
@ -197,13 +123,13 @@ public class RpcController extends BaseController {
RpcError error = rpcError.get(); RpcError error = rpcError.get();
switch (error) { switch (error) {
case TIMEOUT: case TIMEOUT:
responseWriter.setResult(new ResponseEntity<>(HttpStatus.REQUEST_TIMEOUT)); responseWriter.setResult(new ResponseEntity<>(timeoutStatus));
break; break;
case NO_ACTIVE_CONNECTION: case NO_ACTIVE_CONNECTION:
responseWriter.setResult(new ResponseEntity<>(HttpStatus.CONFLICT)); responseWriter.setResult(new ResponseEntity<>(noActiveConnectionStatus));
break; break;
default: default:
responseWriter.setResult(new ResponseEntity<>(HttpStatus.REQUEST_TIMEOUT)); responseWriter.setResult(new ResponseEntity<>(timeoutStatus));
break; break;
} }
} else { } else {
@ -212,8 +138,8 @@ public class RpcController extends BaseController {
String data = responseData.get(); String data = responseData.get();
try { try {
logRpcCall(rpcRequest, rpcError, null); logRpcCall(rpcRequest, rpcError, null);
responseWriter.setResult(new ResponseEntity<>(jsonMapper.readTree(data), HttpStatus.OK)); responseWriter.setResult(new ResponseEntity<>(JacksonUtil.toJsonNode(data), HttpStatus.OK));
} catch (IOException e) { } catch (IllegalArgumentException e) {
log.debug("Failed to decode device response: {}", data, e); log.debug("Failed to decode device response: {}", data, e);
logRpcCall(rpcRequest, rpcError, e); logRpcCall(rpcRequest, rpcError, e);
responseWriter.setResult(new ResponseEntity<>(HttpStatus.NOT_ACCEPTABLE)); responseWriter.setResult(new ResponseEntity<>(HttpStatus.NOT_ACCEPTABLE));

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

@ -67,7 +67,7 @@ public class AdminController extends BaseController {
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));
if (adminSettings.getKey().equals("mail")) { if (adminSettings.getKey().equals("mail")) {
((ObjectNode) adminSettings.getJsonValue()).put("password", ""); ((ObjectNode) adminSettings.getJsonValue()).remove("password");
} }
return adminSettings; return adminSettings;
} catch (Exception e) { } catch (Exception e) {
@ -84,7 +84,7 @@ public class AdminController extends BaseController {
adminSettings = checkNotNull(adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, adminSettings)); adminSettings = checkNotNull(adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, adminSettings));
if (adminSettings.getKey().equals("mail")) { if (adminSettings.getKey().equals("mail")) {
mailService.updateMailConfiguration(); mailService.updateMailConfiguration();
((ObjectNode) adminSettings.getJsonValue()).put("password", ""); ((ObjectNode) adminSettings.getJsonValue()).remove("password");
} else if (adminSettings.getKey().equals("sms")) { } else if (adminSettings.getKey().equals("sms")) {
smsService.updateSmsConfiguration(); smsService.updateSmsConfiguration();
} }
@ -127,6 +127,10 @@ public class AdminController extends BaseController {
accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ); accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ);
adminSettings = checkNotNull(adminSettings); adminSettings = checkNotNull(adminSettings);
if (adminSettings.getKey().equals("mail")) { if (adminSettings.getKey().equals("mail")) {
if(!adminSettings.getJsonValue().has("password")) {
AdminSettings mailSettings = checkNotNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "mail"));
((ObjectNode) adminSettings.getJsonValue()).put("password", mailSettings.getJsonValue().get("password").asText());
}
String email = getCurrentUser().getEmail(); String email = getCurrentUser().getEmail();
mailService.sendTestMail(adminSettings.getJsonValue(), email); mailService.sendTestMail(adminSettings.getJsonValue(), email);
} }

8
application/src/main/java/org/thingsboard/server/controller/AlarmController.java

@ -38,6 +38,7 @@ import org.thingsboard.server.common.data.edge.EdgeEventActionType;
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.data.id.AlarmId; import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
@ -46,6 +47,8 @@ import org.thingsboard.server.queue.util.TbCoreComponent;
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.security.permission.Resource;
import java.util.List;
@RestController @RestController
@TbCoreComponent @TbCoreComponent
@RequestMapping("/api") @RequestMapping("/api")
@ -112,10 +115,13 @@ public class AlarmController extends BaseController {
AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); AlarmId alarmId = new AlarmId(toUUID(strAlarmId));
Alarm alarm = checkAlarmId(alarmId, Operation.WRITE); Alarm alarm = checkAlarmId(alarmId, Operation.WRITE);
List<EdgeId> relatedEdgeIds = findRelatedEdgeIds(getTenantId(), alarm.getOriginator());
logEntityAction(alarm.getOriginator(), alarm, logEntityAction(alarm.getOriginator(), alarm,
getCurrentUser().getCustomerId(), getCurrentUser().getCustomerId(),
ActionType.ALARM_DELETE, null); ActionType.ALARM_DELETE, null);
sendEntityNotificationMsg(getTenantId(), alarmId, EdgeEventActionType.DELETED);
sendAlarmDeleteNotificationMsg(getTenantId(), alarmId, relatedEdgeIds, alarm);
return alarmService.deleteAlarm(getTenantId(), alarmId); return alarmService.deleteAlarm(getTenantId(), alarmId);
} catch (Exception e) { } catch (Exception e) {

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

@ -27,13 +27,13 @@ import org.springframework.http.MediaType;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.DashboardInfo; import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceInfo; import org.thingsboard.server.common.data.DeviceInfo;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.EntityViewInfo; import org.thingsboard.server.common.data.EntityViewInfo;
@ -118,18 +118,17 @@ import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.dao.widget.WidgetTypeService; import org.thingsboard.server.dao.widget.WidgetTypeService;
import org.thingsboard.server.dao.widget.WidgetsBundleService; import org.thingsboard.server.dao.widget.WidgetsBundleService;
import org.thingsboard.server.exception.ThingsboardErrorResponseHandler; import org.thingsboard.server.exception.ThingsboardErrorResponseHandler;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.action.RuleEngineEntityActionService; import org.thingsboard.server.service.action.RuleEngineEntityActionService;
import org.thingsboard.server.service.component.ComponentDiscoveryService; import org.thingsboard.server.service.component.ComponentDiscoveryService;
import org.thingsboard.server.service.edge.EdgeLicenseService;
import org.thingsboard.server.service.edge.EdgeNotificationService; import org.thingsboard.server.service.edge.EdgeNotificationService;
import org.thingsboard.server.service.edge.rpc.EdgeRpcService; import org.thingsboard.server.service.edge.rpc.EdgeRpcService;
import org.thingsboard.server.service.lwm2m.LwM2MServerSecurityInfoRepository; import org.thingsboard.server.service.lwm2m.LwM2MServerSecurityInfoRepository;
import org.thingsboard.server.service.ota.OtaPackageStateService; import org.thingsboard.server.service.ota.OtaPackageStateService;
import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.queue.TbClusterService;
import org.thingsboard.server.service.resource.TbResourceService; import org.thingsboard.server.service.resource.TbResourceService;
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;
@ -276,6 +275,9 @@ public abstract class BaseController {
@Autowired(required = false) @Autowired(required = false)
protected EdgeRpcService edgeGrpcService; protected EdgeRpcService edgeGrpcService;
@Autowired(required = false)
protected EdgeLicenseService edgeLicenseService;
@Autowired @Autowired
protected RuleEngineEntityActionService ruleEngineEntityActionService; protected RuleEngineEntityActionService ruleEngineEntityActionService;
@ -852,13 +854,25 @@ public abstract class BaseController {
} }
protected void sendDeleteNotificationMsg(TenantId tenantId, EntityId entityId, List<EdgeId> edgeIds) { protected void sendDeleteNotificationMsg(TenantId tenantId, EntityId entityId, List<EdgeId> edgeIds) {
sendDeleteNotificationMsg(tenantId, entityId, edgeIds, null);
}
protected void sendDeleteNotificationMsg(TenantId tenantId, EntityId entityId, List<EdgeId> edgeIds, String body) {
if (edgeIds != null && !edgeIds.isEmpty()) { if (edgeIds != null && !edgeIds.isEmpty()) {
for (EdgeId edgeId : edgeIds) { for (EdgeId edgeId : edgeIds) {
sendNotificationMsgToEdgeService(tenantId, edgeId, entityId, null, null, EdgeEventActionType.DELETED); sendNotificationMsgToEdgeService(tenantId, edgeId, entityId, body, null, EdgeEventActionType.DELETED);
} }
} }
} }
protected void sendAlarmDeleteNotificationMsg(TenantId tenantId, EntityId entityId, List<EdgeId> edgeIds, Alarm alarm) {
try {
sendDeleteNotificationMsg(tenantId, entityId, edgeIds, json.writeValueAsString(alarm));
} catch (Exception e) {
log.warn("Failed to push delete alarm msg to core: {}", alarm, e);
}
}
protected void sendEntityAssignToCustomerNotificationMsg(TenantId tenantId, EntityId entityId, CustomerId customerId, EdgeEventActionType action) { protected void sendEntityAssignToCustomerNotificationMsg(TenantId tenantId, EntityId entityId, CustomerId customerId, EdgeEventActionType action) {
try { try {
sendNotificationMsgToEdgeService(tenantId, null, entityId, json.writeValueAsString(customerId), null, action); sendNotificationMsgToEdgeService(tenantId, null, entityId, json.writeValueAsString(customerId), null, action);
@ -876,42 +890,7 @@ public abstract class BaseController {
} }
private void sendNotificationMsgToEdgeService(TenantId tenantId, EdgeId edgeId, EntityId entityId, String body, EdgeEventType type, EdgeEventActionType action) { private void sendNotificationMsgToEdgeService(TenantId tenantId, EdgeId edgeId, EntityId entityId, String body, EdgeEventType type, EdgeEventActionType action) {
if (!edgesEnabled) { tbClusterService.sendNotificationMsgToEdgeService(tenantId, edgeId, entityId, body, type, action);
return;
}
if (type == null) {
if (entityId != null) {
type = EdgeUtils.getEdgeEventTypeByEntityType(entityId.getEntityType());
} else {
log.trace("[{}] entity id and type are null. Ignoring this notification", tenantId);
return;
}
if (type == null) {
log.trace("[{}] edge event type is null. Ignoring this notification [{}]", tenantId, entityId);
return;
}
}
TransportProtos.EdgeNotificationMsgProto.Builder builder = TransportProtos.EdgeNotificationMsgProto.newBuilder();
builder.setTenantIdMSB(tenantId.getId().getMostSignificantBits());
builder.setTenantIdLSB(tenantId.getId().getLeastSignificantBits());
builder.setType(type.name());
builder.setAction(action.name());
if (entityId != null) {
builder.setEntityIdMSB(entityId.getId().getMostSignificantBits());
builder.setEntityIdLSB(entityId.getId().getLeastSignificantBits());
builder.setEntityType(entityId.getEntityType().name());
}
if (edgeId != null) {
builder.setEdgeIdMSB(edgeId.getId().getMostSignificantBits());
builder.setEdgeIdLSB(edgeId.getId().getLeastSignificantBits());
}
if (body != null) {
builder.setBody(body);
}
TransportProtos.EdgeNotificationMsgProto msg = builder.build();
log.trace("[{}] sending notification to edge service {}", tenantId.getId(), msg);
tbClusterService.pushMsgToCore(tenantId, entityId != null ? entityId : tenantId,
TransportProtos.ToCoreMsg.newBuilder().setEdgeNotificationMsg(msg).build(), null);
} }
protected List<EdgeId> findRelatedEdgeIds(TenantId tenantId, EntityId entityId) { protected List<EdgeId> findRelatedEdgeIds(TenantId tenantId, EntityId entityId) {

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

@ -149,7 +149,7 @@ public class CustomerController extends BaseController {
ActionType.DELETED, null, strCustomerId); ActionType.DELETED, null, strCustomerId);
sendDeleteNotificationMsg(getTenantId(), customerId, relatedEdgeIds); sendDeleteNotificationMsg(getTenantId(), customerId, relatedEdgeIds);
tbClusterService.onEntityStateChange(getTenantId(), customerId, ComponentLifecycleEvent.DELETED); tbClusterService.broadcastEntityStateChangeEvent(getTenantId(), customerId, ComponentLifecycleEvent.DELETED);
} catch (Exception e) { } catch (Exception e) {
logEntityAction(emptyId(EntityType.CUSTOMER), logEntityAction(emptyId(EntityType.CUSTOMER),

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

@ -468,13 +468,18 @@ public class DashboardController extends BaseController {
public PageData<DashboardInfo> getTenantDashboards( public PageData<DashboardInfo> getTenantDashboards(
@RequestParam int pageSize, @RequestParam int pageSize,
@RequestParam int page, @RequestParam int page,
@RequestParam(required = false) Boolean mobile,
@RequestParam(required = false) String textSearch, @RequestParam(required = false) String textSearch,
@RequestParam(required = false) String sortProperty, @RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder) throws ThingsboardException { @RequestParam(required = false) String sortOrder) throws ThingsboardException {
try { try {
TenantId tenantId = getCurrentUser().getTenantId(); TenantId tenantId = getCurrentUser().getTenantId();
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
return checkNotNull(dashboardService.findDashboardsByTenantId(tenantId, pageLink)); if (mobile != null && mobile.booleanValue()) {
return checkNotNull(dashboardService.findMobileDashboardsByTenantId(tenantId, pageLink));
} else {
return checkNotNull(dashboardService.findDashboardsByTenantId(tenantId, pageLink));
}
} catch (Exception e) { } catch (Exception e) {
throw handleException(e); throw handleException(e);
} }
@ -487,6 +492,7 @@ public class DashboardController extends BaseController {
@PathVariable("customerId") String strCustomerId, @PathVariable("customerId") String strCustomerId,
@RequestParam int pageSize, @RequestParam int pageSize,
@RequestParam int page, @RequestParam int page,
@RequestParam(required = false) Boolean mobile,
@RequestParam(required = false) String textSearch, @RequestParam(required = false) String textSearch,
@RequestParam(required = false) String sortProperty, @RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder) throws ThingsboardException { @RequestParam(required = false) String sortOrder) throws ThingsboardException {
@ -496,7 +502,11 @@ public class DashboardController extends BaseController {
CustomerId customerId = new CustomerId(toUUID(strCustomerId)); CustomerId customerId = new CustomerId(toUUID(strCustomerId));
checkCustomerId(customerId, Operation.READ); checkCustomerId(customerId, Operation.READ);
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
return checkNotNull(dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink)); if (mobile != null && mobile.booleanValue()) {
return checkNotNull(dashboardService.findMobileDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink));
} else {
return checkNotNull(dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink));
}
} catch (Exception e) { } catch (Exception e) {
throw handleException(e); throw handleException(e);
} }

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

@ -33,7 +33,6 @@ import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult; import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.rule.engine.api.msg.DeviceCredentialsUpdateNotificationMsg; import org.thingsboard.rule.engine.api.msg.DeviceCredentialsUpdateNotificationMsg;
import org.thingsboard.rule.engine.api.msg.DeviceEdgeUpdateMsg; import org.thingsboard.rule.engine.api.msg.DeviceEdgeUpdateMsg;
import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg;
import org.thingsboard.server.common.data.ClaimRequest; import org.thingsboard.server.common.data.ClaimRequest;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
@ -125,38 +124,21 @@ public class DeviceController extends BaseController {
try { try {
device.setTenantId(getCurrentUser().getTenantId()); device.setTenantId(getCurrentUser().getTenantId());
checkEntity(device.getId(), device, Resource.DEVICE); Device oldDevice = null;
Device oldDevice;
if (!created) { if (!created) {
oldDevice = deviceService.findDeviceById(getTenantId(), device.getId()); oldDevice = checkDeviceId(device.getId(), Operation.WRITE);
} else { } else {
oldDevice = null; checkEntity(null, device, Resource.DEVICE);
} }
Device savedDevice = checkNotNull(deviceService.saveDeviceWithAccessToken(device, accessToken)); Device savedDevice = checkNotNull(deviceService.saveDeviceWithAccessToken(device, accessToken));
tbClusterService.onDeviceChange(savedDevice, null); tbClusterService.onDeviceUpdated(savedDevice, oldDevice);
tbClusterService.pushMsgToCore(new DeviceNameOrTypeUpdateMsg(savedDevice.getTenantId(),
savedDevice.getId(), savedDevice.getName(), savedDevice.getType()), null);
tbClusterService.onEntityStateChange(savedDevice.getTenantId(), savedDevice.getId(), created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
if (!created) {
sendEntityNotificationMsg(savedDevice.getTenantId(), savedDevice.getId(), EdgeEventActionType.UPDATED);
}
logEntityAction(savedDevice.getId(), savedDevice, logEntityAction(savedDevice.getId(), savedDevice,
savedDevice.getCustomerId(), savedDevice.getCustomerId(),
created ? ActionType.ADDED : ActionType.UPDATED, null); created ? ActionType.ADDED : ActionType.UPDATED, null);
if (device.getId() == null) {
deviceStateService.onDeviceAdded(savedDevice);
} else {
deviceStateService.onDeviceUpdated(savedDevice);
}
otaPackageStateService.update(savedDevice, oldDevice);
return savedDevice; return savedDevice;
} catch (Exception e) { } catch (Exception e) {
logEntityAction(emptyId(EntityType.DEVICE), device, logEntityAction(emptyId(EntityType.DEVICE), device,
@ -180,15 +162,12 @@ public class DeviceController extends BaseController {
deviceService.deleteDevice(getCurrentUser().getTenantId(), deviceId); deviceService.deleteDevice(getCurrentUser().getTenantId(), deviceId);
tbClusterService.onDeviceDeleted(device, null); tbClusterService.onDeviceDeleted(device, null);
tbClusterService.onEntityStateChange(device.getTenantId(), deviceId, ComponentLifecycleEvent.DELETED);
logEntityAction(deviceId, device, logEntityAction(deviceId, device,
device.getCustomerId(), device.getCustomerId(),
ActionType.DELETED, null, strDeviceId); ActionType.DELETED, null, strDeviceId);
sendDeleteNotificationMsg(getTenantId(), deviceId, relatedEdgeIds); sendDeleteNotificationMsg(getTenantId(), deviceId, relatedEdgeIds);
deviceStateService.onDeviceDeleted(device);
} catch (Exception e) { } catch (Exception e) {
logEntityAction(emptyId(EntityType.DEVICE), logEntityAction(emptyId(EntityType.DEVICE),
null, null,

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

@ -161,7 +161,7 @@ public class DeviceProfileController extends BaseController {
DeviceProfile savedDeviceProfile = checkNotNull(deviceProfileService.saveDeviceProfile(deviceProfile)); DeviceProfile savedDeviceProfile = checkNotNull(deviceProfileService.saveDeviceProfile(deviceProfile));
tbClusterService.onDeviceProfileChange(savedDeviceProfile, null); tbClusterService.onDeviceProfileChange(savedDeviceProfile, null);
tbClusterService.onEntityStateChange(deviceProfile.getTenantId(), savedDeviceProfile.getId(), tbClusterService.broadcastEntityStateChangeEvent(deviceProfile.getTenantId(), savedDeviceProfile.getId(),
created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
logEntityAction(savedDeviceProfile.getId(), savedDeviceProfile, logEntityAction(savedDeviceProfile.getId(), savedDeviceProfile,
@ -191,7 +191,7 @@ public class DeviceProfileController extends BaseController {
deviceProfileService.deleteDeviceProfile(getTenantId(), deviceProfileId); deviceProfileService.deleteDeviceProfile(getTenantId(), deviceProfileId);
tbClusterService.onDeviceProfileDelete(deviceProfile, null); tbClusterService.onDeviceProfileDelete(deviceProfile, null);
tbClusterService.onEntityStateChange(deviceProfile.getTenantId(), deviceProfile.getId(), ComponentLifecycleEvent.DELETED); tbClusterService.broadcastEntityStateChangeEvent(deviceProfile.getTenantId(), deviceProfile.getId(), ComponentLifecycleEvent.DELETED);
logEntityAction(deviceProfileId, deviceProfile, logEntityAction(deviceProfileId, deviceProfile,
null, null,

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

@ -15,8 +15,11 @@
*/ */
package org.thingsboard.server.controller; package org.thingsboard.server.controller;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
@ -59,6 +62,7 @@ import java.util.stream.Collectors;
@RestController @RestController
@TbCoreComponent @TbCoreComponent
@Slf4j
@RequestMapping("/api") @RequestMapping("/api")
public class EdgeController extends BaseController { public class EdgeController extends BaseController {
@ -135,7 +139,7 @@ public class EdgeController extends BaseController {
edgeService.assignDefaultRuleChainsToEdge(tenantId, savedEdge.getId()); edgeService.assignDefaultRuleChainsToEdge(tenantId, savedEdge.getId());
} }
tbClusterService.onEntityStateChange(savedEdge.getTenantId(), savedEdge.getId(), tbClusterService.broadcastEntityStateChangeEvent(savedEdge.getTenantId(), savedEdge.getId(),
created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
logEntityAction(savedEdge.getId(), savedEdge, null, created ? ActionType.ADDED : ActionType.UPDATED, null); logEntityAction(savedEdge.getId(), savedEdge, null, created ? ActionType.ADDED : ActionType.UPDATED, null);
@ -157,7 +161,7 @@ public class EdgeController extends BaseController {
Edge edge = checkEdgeId(edgeId, Operation.DELETE); Edge edge = checkEdgeId(edgeId, Operation.DELETE);
edgeService.deleteEdge(getTenantId(), edgeId); edgeService.deleteEdge(getTenantId(), edgeId);
tbClusterService.onEntityStateChange(getTenantId(), edgeId, tbClusterService.broadcastEntityStateChangeEvent(getTenantId(), edgeId,
ComponentLifecycleEvent.DELETED); ComponentLifecycleEvent.DELETED);
logEntityAction(edgeId, edge, logEntityAction(edgeId, edge,
@ -208,7 +212,7 @@ public class EdgeController extends BaseController {
Edge savedEdge = checkNotNull(edgeService.assignEdgeToCustomer(getCurrentUser().getTenantId(), edgeId, customerId)); Edge savedEdge = checkNotNull(edgeService.assignEdgeToCustomer(getCurrentUser().getTenantId(), edgeId, customerId));
tbClusterService.onEntityStateChange(getTenantId(), edgeId, tbClusterService.broadcastEntityStateChangeEvent(getTenantId(), edgeId,
ComponentLifecycleEvent.UPDATED); ComponentLifecycleEvent.UPDATED);
logEntityAction(edgeId, savedEdge, logEntityAction(edgeId, savedEdge,
@ -242,7 +246,7 @@ public class EdgeController extends BaseController {
Edge savedEdge = checkNotNull(edgeService.unassignEdgeFromCustomer(getCurrentUser().getTenantId(), edgeId)); Edge savedEdge = checkNotNull(edgeService.unassignEdgeFromCustomer(getCurrentUser().getTenantId(), edgeId));
tbClusterService.onEntityStateChange(getTenantId(), edgeId, tbClusterService.broadcastEntityStateChangeEvent(getTenantId(), edgeId,
ComponentLifecycleEvent.UPDATED); ComponentLifecycleEvent.UPDATED);
logEntityAction(edgeId, edge, logEntityAction(edgeId, edge,
@ -272,7 +276,7 @@ public class EdgeController extends BaseController {
Customer publicCustomer = customerService.findOrCreatePublicCustomer(edge.getTenantId()); Customer publicCustomer = customerService.findOrCreatePublicCustomer(edge.getTenantId());
Edge savedEdge = checkNotNull(edgeService.assignEdgeToCustomer(getCurrentUser().getTenantId(), edgeId, publicCustomer.getId())); Edge savedEdge = checkNotNull(edgeService.assignEdgeToCustomer(getCurrentUser().getTenantId(), edgeId, publicCustomer.getId()));
tbClusterService.onEntityStateChange(getTenantId(), edgeId, tbClusterService.broadcastEntityStateChangeEvent(getTenantId(), edgeId,
ComponentLifecycleEvent.UPDATED); ComponentLifecycleEvent.UPDATED);
logEntityAction(edgeId, savedEdge, logEntityAction(edgeId, savedEdge,
@ -364,7 +368,7 @@ public class EdgeController extends BaseController {
Edge updatedEdge = edgeNotificationService.setEdgeRootRuleChain(getTenantId(), edge, ruleChainId); Edge updatedEdge = edgeNotificationService.setEdgeRootRuleChain(getTenantId(), edge, ruleChainId);
tbClusterService.onEntityStateChange(updatedEdge.getTenantId(), updatedEdge.getId(), ComponentLifecycleEvent.UPDATED); tbClusterService.broadcastEntityStateChangeEvent(updatedEdge.getTenantId(), updatedEdge.getId(), ComponentLifecycleEvent.UPDATED);
logEntityAction(updatedEdge.getId(), updatedEdge, null, ActionType.UPDATED, null); logEntityAction(updatedEdge.getId(), updatedEdge, null, ActionType.UPDATED, null);
@ -544,27 +548,6 @@ public class EdgeController extends BaseController {
} }
} }
@RequestMapping(value = "/license/checkInstance", method = RequestMethod.POST)
@ResponseBody
public Object checkInstance(@RequestBody Object request) throws ThingsboardException {
try {
return edgeService.checkInstance(request);
} catch (Exception e) {
throw new ThingsboardException(e, ThingsboardErrorCode.SUBSCRIPTION_VIOLATION);
}
}
@RequestMapping(value = "/license/activateInstance", params = {"licenseSecret", "releaseDate"}, method = RequestMethod.POST)
@ResponseBody
public Object activateInstance(@RequestParam String licenseSecret,
@RequestParam String releaseDate) throws ThingsboardException {
try {
return edgeService.activateInstance(licenseSecret, releaseDate);
} catch (Exception e) {
throw new ThingsboardException(e, ThingsboardErrorCode.SUBSCRIPTION_VIOLATION);
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/missingToRelatedRuleChains/{edgeId}", method = RequestMethod.GET) @RequestMapping(value = "/edge/missingToRelatedRuleChains/{edgeId}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -583,4 +566,29 @@ public class EdgeController extends BaseController {
private void cleanUpLicenseKey(Edge edge) { private void cleanUpLicenseKey(Edge edge) {
edge.setEdgeLicenseKey(null); edge.setEdgeLicenseKey(null);
} }
@RequestMapping(value = "/license/checkInstance", method = RequestMethod.POST)
@ResponseBody
public ResponseEntity<JsonNode> checkInstance(@RequestBody JsonNode request) throws ThingsboardException {
log.debug("Checking instance [{}]", request);
try {
return edgeLicenseService.checkInstance(request);
} catch (Exception e) {
log.error("Error occurred: [{}]", e.getMessage(), e);
throw new ThingsboardException(e, ThingsboardErrorCode.SUBSCRIPTION_VIOLATION);
}
}
@RequestMapping(value = "/license/activateInstance", params = {"licenseSecret", "releaseDate"}, method = RequestMethod.POST)
@ResponseBody
public ResponseEntity<JsonNode> activateInstance(@RequestParam String licenseSecret,
@RequestParam String releaseDate) throws ThingsboardException {
log.debug("Activating instance [{}], [{}]", licenseSecret, releaseDate);
try {
return edgeLicenseService.activateInstance(licenseSecret, releaseDate);
} catch (Exception e) {
log.error("Error occurred: [{}]", e.getMessage(), e);
throw new ThingsboardException(e, ThingsboardErrorCode.SUBSCRIPTION_VIOLATION);
}
}
} }

15
application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java

@ -24,13 +24,11 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.lwm2m.ServerSecurityConfig; import org.thingsboard.server.common.data.lwm2m.ServerSecurityConfig;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.permission.Resource; import org.thingsboard.server.service.security.permission.Resource;
@ -66,22 +64,11 @@ public class Lwm2mController extends BaseController {
checkEntity(device.getId(), device, Resource.DEVICE); checkEntity(device.getId(), device, Resource.DEVICE);
Device savedDevice = deviceService.saveDeviceWithCredentials(device, credentials); Device savedDevice = deviceService.saveDeviceWithCredentials(device, credentials);
checkNotNull(savedDevice); checkNotNull(savedDevice);
tbClusterService.onDeviceUpdated(savedDevice, device);
tbClusterService.onDeviceChange(savedDevice, null);
tbClusterService.pushMsgToCore(new DeviceNameOrTypeUpdateMsg(savedDevice.getTenantId(),
savedDevice.getId(), savedDevice.getName(), savedDevice.getType()), null);
tbClusterService.onEntityStateChange(savedDevice.getTenantId(), savedDevice.getId(),
device.getId() == null ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
logEntityAction(savedDevice.getId(), savedDevice, logEntityAction(savedDevice.getId(), savedDevice,
savedDevice.getCustomerId(), savedDevice.getCustomerId(),
device.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null); device.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
if (device.getId() == null) {
deviceStateService.onDeviceAdded(savedDevice);
} else {
deviceStateService.onDeviceUpdated(savedDevice);
}
return savedDevice; return savedDevice;
} catch (Exception e) { } catch (Exception e) {
logEntityAction(emptyId(EntityType.DEVICE), device, logEntityAction(emptyId(EntityType.DEVICE), device,

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

@ -146,6 +146,7 @@ public class OtaPackageController extends BaseController {
otaPackage.setType(info.getType()); otaPackage.setType(info.getType());
otaPackage.setTitle(info.getTitle()); otaPackage.setTitle(info.getTitle());
otaPackage.setVersion(info.getVersion()); otaPackage.setVersion(info.getVersion());
otaPackage.setTag(info.getTag());
otaPackage.setAdditionalInfo(info.getAdditionalInfo()); otaPackage.setAdditionalInfo(info.getAdditionalInfo());
ChecksumAlgorithm checksumAlgorithm = ChecksumAlgorithm.valueOf(checksumAlgorithmStr.toUpperCase()); ChecksumAlgorithm checksumAlgorithm = ChecksumAlgorithm.valueOf(checksumAlgorithmStr.toUpperCase());

29
application/src/main/java/org/thingsboard/server/controller/QueueController.java

@ -15,7 +15,7 @@
*/ */
package org.thingsboard.server.controller; package org.thingsboard.server.controller;
import org.springframework.beans.factory.annotation.Autowired; import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
@ -24,41 +24,26 @@ import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings; import org.thingsboard.server.queue.QueueService;
import org.thingsboard.server.queue.settings.TbRuleEngineQueueConfiguration;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import java.util.Arrays; import java.util.Set;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@RestController @RestController
@TbCoreComponent @TbCoreComponent
@RequestMapping("/api") @RequestMapping("/api")
@RequiredArgsConstructor
public class QueueController extends BaseController { public class QueueController extends BaseController {
@Autowired(required = false) private final QueueService queueService;
private TbQueueRuleEngineSettings ruleEngineSettings;
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/queues", params = {"serviceType"}, method = RequestMethod.GET) @RequestMapping(value = "/tenant/queues", params = {"serviceType"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public List<String> getTenantQueuesByServiceType(@RequestParam String serviceType) throws ThingsboardException { public Set<String> getTenantQueuesByServiceType(@RequestParam String serviceType) throws ThingsboardException {
checkParameter("serviceType", serviceType); checkParameter("serviceType", serviceType);
try { try {
ServiceType type = ServiceType.valueOf(serviceType); return queueService.getQueuesByServiceType(ServiceType.valueOf(serviceType));
switch (type) {
case TB_RULE_ENGINE:
if (ruleEngineSettings == null) {
return Arrays.asList("Main", "HighPriority", "SequentialByOriginator");
}
return ruleEngineSettings.getQueues().stream()
.map(TbRuleEngineQueueConfiguration::getName)
.collect(Collectors.toList());
default:
return Collections.emptyList();
}
} catch (Exception e) { } catch (Exception e) {
throw handleException(e); throw handleException(e);
} }

55
application/src/main/java/org/thingsboard/server/controller/RpcV1Controller.java

@ -0,0 +1,55 @@
/**
* 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.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.queue.util.TbCoreComponent;
import java.util.UUID;
@RestController
@TbCoreComponent
@RequestMapping(TbUrlConstants.RPC_V1_URL_PREFIX)
@Slf4j
public class RpcV1Controller extends AbstractRpcController {
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/oneway/{deviceId}", method = RequestMethod.POST)
@ResponseBody
public DeferredResult<ResponseEntity> handleOneWayDeviceRPCRequest(@PathVariable("deviceId") String deviceIdStr, @RequestBody String requestBody) throws ThingsboardException {
return handleDeviceRPCRequest(true, new DeviceId(UUID.fromString(deviceIdStr)), requestBody, HttpStatus.REQUEST_TIMEOUT, HttpStatus.CONFLICT);
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/twoway/{deviceId}", method = RequestMethod.POST)
@ResponseBody
public DeferredResult<ResponseEntity> handleTwoWayDeviceRPCRequest(@PathVariable("deviceId") String deviceIdStr, @RequestBody String requestBody) throws ThingsboardException {
return handleDeviceRPCRequest(false, new DeviceId(UUID.fromString(deviceIdStr)), requestBody, HttpStatus.REQUEST_TIMEOUT, HttpStatus.CONFLICT);
}
}

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

@ -0,0 +1,128 @@
/**
* 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.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.RpcId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.rpc.Rpc;
import org.thingsboard.server.common.data.rpc.RpcStatus;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.rpc.RemoveRpcActorMsg;
import org.thingsboard.server.service.security.permission.Operation;
import java.util.UUID;
import static org.thingsboard.server.common.data.DataConstants.RPC_DELETED;
@RestController
@TbCoreComponent
@RequestMapping(TbUrlConstants.RPC_V2_URL_PREFIX)
@Slf4j
public class RpcV2Controller extends AbstractRpcController {
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/oneway/{deviceId}", method = RequestMethod.POST)
@ResponseBody
public DeferredResult<ResponseEntity> handleOneWayDeviceRPCRequest(@PathVariable("deviceId") String deviceIdStr, @RequestBody String requestBody) throws ThingsboardException {
return handleDeviceRPCRequest(true, new DeviceId(UUID.fromString(deviceIdStr)), requestBody, HttpStatus.GATEWAY_TIMEOUT, HttpStatus.GATEWAY_TIMEOUT);
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/twoway/{deviceId}", method = RequestMethod.POST)
@ResponseBody
public DeferredResult<ResponseEntity> handleTwoWayDeviceRPCRequest(@PathVariable("deviceId") String deviceIdStr, @RequestBody String requestBody) throws ThingsboardException {
return handleDeviceRPCRequest(false, new DeviceId(UUID.fromString(deviceIdStr)), requestBody, HttpStatus.GATEWAY_TIMEOUT, HttpStatus.GATEWAY_TIMEOUT);
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/persistent/{rpcId}", method = RequestMethod.GET)
@ResponseBody
public Rpc getPersistedRpc(@PathVariable("rpcId") String strRpc) throws ThingsboardException {
checkParameter("RpcId", strRpc);
try {
RpcId rpcId = new RpcId(UUID.fromString(strRpc));
return checkRpcId(rpcId, Operation.READ);
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/persistent/device/{deviceId}", method = RequestMethod.GET)
@ResponseBody
public PageData<Rpc> getPersistedRpcByDevice(@PathVariable("deviceId") String strDeviceId,
@RequestParam int pageSize,
@RequestParam int page,
@RequestParam RpcStatus rpcStatus,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
checkParameter("DeviceId", strDeviceId);
try {
TenantId tenantId = getCurrentUser().getTenantId();
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
DeviceId deviceId = new DeviceId(UUID.fromString(strDeviceId));
return checkNotNull(rpcService.findAllByDeviceIdAndStatus(tenantId, deviceId, rpcStatus, pageLink));
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/persistent/{rpcId}", method = RequestMethod.DELETE)
@ResponseBody
public void deleteResource(@PathVariable("rpcId") String strRpc) throws ThingsboardException {
checkParameter("RpcId", strRpc);
try {
RpcId rpcId = new RpcId(UUID.fromString(strRpc));
Rpc rpc = checkRpcId(rpcId, Operation.DELETE);
if (rpc != null) {
if (rpc.getStatus().equals(RpcStatus.QUEUED)) {
RemoveRpcActorMsg removeMsg = new RemoveRpcActorMsg(getTenantId(), rpc.getDeviceId(), rpc.getUuidId());
log.trace("[{}] Forwarding msg {} to queue actor!", rpc.getDeviceId(), rpc);
tbClusterService.pushMsgToCore(removeMsg, null);
}
rpcService.deleteRpc(getTenantId(), rpcId);
TbMsg msg = TbMsg.newMsg(RPC_DELETED, rpc.getDeviceId(), TbMsgMetaData.EMPTY, JacksonUtil.toString(rpc));
tbClusterService.pushMsgToRuleEngine(getTenantId(), rpc.getDeviceId(), msg, null);
}
} catch (Exception e) {
throw handleException(e);
}
}
}

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

@ -149,7 +149,7 @@ public class RuleChainController extends BaseController {
RuleChain savedRuleChain = checkNotNull(ruleChainService.saveRuleChain(ruleChain)); RuleChain savedRuleChain = checkNotNull(ruleChainService.saveRuleChain(ruleChain));
if (RuleChainType.CORE.equals(savedRuleChain.getType())) { if (RuleChainType.CORE.equals(savedRuleChain.getType())) {
tbClusterService.onEntityStateChange(ruleChain.getTenantId(), savedRuleChain.getId(), tbClusterService.broadcastEntityStateChangeEvent(ruleChain.getTenantId(), savedRuleChain.getId(),
created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
} }
@ -183,7 +183,7 @@ public class RuleChainController extends BaseController {
RuleChain savedRuleChain = installScripts.createDefaultRuleChain(getCurrentUser().getTenantId(), request.getName()); RuleChain savedRuleChain = installScripts.createDefaultRuleChain(getCurrentUser().getTenantId(), request.getName());
tbClusterService.onEntityStateChange(savedRuleChain.getTenantId(), savedRuleChain.getId(), ComponentLifecycleEvent.CREATED); tbClusterService.broadcastEntityStateChangeEvent(savedRuleChain.getTenantId(), savedRuleChain.getId(), ComponentLifecycleEvent.CREATED);
logEntityAction(savedRuleChain.getId(), savedRuleChain, null, ActionType.ADDED, null); logEntityAction(savedRuleChain.getId(), savedRuleChain, null, ActionType.ADDED, null);
@ -210,7 +210,7 @@ public class RuleChainController extends BaseController {
if (previousRootRuleChain != null) { if (previousRootRuleChain != null) {
previousRootRuleChain = ruleChainService.findRuleChainById(getTenantId(), previousRootRuleChain.getId()); previousRootRuleChain = ruleChainService.findRuleChainById(getTenantId(), previousRootRuleChain.getId());
tbClusterService.onEntityStateChange(previousRootRuleChain.getTenantId(), previousRootRuleChain.getId(), tbClusterService.broadcastEntityStateChangeEvent(previousRootRuleChain.getTenantId(), previousRootRuleChain.getId(),
ComponentLifecycleEvent.UPDATED); ComponentLifecycleEvent.UPDATED);
logEntityAction(previousRootRuleChain.getId(), previousRootRuleChain, logEntityAction(previousRootRuleChain.getId(), previousRootRuleChain,
@ -218,7 +218,7 @@ public class RuleChainController extends BaseController {
} }
ruleChain = ruleChainService.findRuleChainById(getTenantId(), ruleChainId); ruleChain = ruleChainService.findRuleChainById(getTenantId(), ruleChainId);
tbClusterService.onEntityStateChange(ruleChain.getTenantId(), ruleChain.getId(), tbClusterService.broadcastEntityStateChangeEvent(ruleChain.getTenantId(), ruleChain.getId(),
ComponentLifecycleEvent.UPDATED); ComponentLifecycleEvent.UPDATED);
logEntityAction(ruleChain.getId(), ruleChain, logEntityAction(ruleChain.getId(), ruleChain,
@ -254,7 +254,7 @@ public class RuleChainController extends BaseController {
RuleChainMetaData savedRuleChainMetaData = checkNotNull(ruleChainService.loadRuleChainMetaData(tenantId, ruleChainMetaData.getRuleChainId())); RuleChainMetaData savedRuleChainMetaData = checkNotNull(ruleChainService.loadRuleChainMetaData(tenantId, ruleChainMetaData.getRuleChainId()));
if (RuleChainType.CORE.equals(ruleChain.getType())) { if (RuleChainType.CORE.equals(ruleChain.getType())) {
tbClusterService.onEntityStateChange(ruleChain.getTenantId(), ruleChain.getId(), ComponentLifecycleEvent.UPDATED); tbClusterService.broadcastEntityStateChangeEvent(ruleChain.getTenantId(), ruleChain.getId(), ComponentLifecycleEvent.UPDATED);
} }
logEntityAction(ruleChain.getId(), ruleChain, logEntityAction(ruleChain.getId(), ruleChain,
@ -323,9 +323,9 @@ public class RuleChainController extends BaseController {
if (RuleChainType.CORE.equals(ruleChain.getType())) { if (RuleChainType.CORE.equals(ruleChain.getType())) {
referencingRuleChainIds.forEach(referencingRuleChainId -> referencingRuleChainIds.forEach(referencingRuleChainId ->
tbClusterService.onEntityStateChange(ruleChain.getTenantId(), referencingRuleChainId, ComponentLifecycleEvent.UPDATED)); tbClusterService.broadcastEntityStateChangeEvent(ruleChain.getTenantId(), referencingRuleChainId, ComponentLifecycleEvent.UPDATED));
tbClusterService.onEntityStateChange(ruleChain.getTenantId(), ruleChain.getId(), ComponentLifecycleEvent.DELETED); tbClusterService.broadcastEntityStateChangeEvent(ruleChain.getTenantId(), ruleChain.getId(), ComponentLifecycleEvent.DELETED);
} }
logEntityAction(ruleChainId, ruleChain, logEntityAction(ruleChainId, ruleChain,
@ -456,7 +456,7 @@ public class RuleChainController extends BaseController {
List<RuleChainImportResult> importResults = ruleChainService.importTenantRuleChains(tenantId, ruleChainData, RuleChainType.CORE, overwrite); List<RuleChainImportResult> importResults = ruleChainService.importTenantRuleChains(tenantId, ruleChainData, RuleChainType.CORE, overwrite);
if (!CollectionUtils.isEmpty(importResults)) { if (!CollectionUtils.isEmpty(importResults)) {
for (RuleChainImportResult importResult : importResults) { for (RuleChainImportResult importResult : importResults) {
tbClusterService.onEntityStateChange(importResult.getTenantId(), importResult.getRuleChainId(), importResult.getLifecycleEvent()); tbClusterService.broadcastEntityStateChangeEvent(importResult.getTenantId(), importResult.getRuleChainId(), importResult.getLifecycleEvent());
} }
} }
} catch (Exception e) { } catch (Exception e) {

3
application/src/main/java/org/thingsboard/server/controller/TbUrlConstants.java

@ -20,5 +20,6 @@ package org.thingsboard.server.controller;
*/ */
public class TbUrlConstants { public class TbUrlConstants {
public static final String TELEMETRY_URL_PREFIX = "/api/plugins/telemetry"; public static final String TELEMETRY_URL_PREFIX = "/api/plugins/telemetry";
public static final String RPC_URL_PREFIX = "/api/plugins/rpc"; public static final String RPC_V1_URL_PREFIX = "/api/plugins/rpc";
public static final String RPC_V2_URL_PREFIX = "/api/rpc";
} }

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

@ -99,7 +99,7 @@ public class TenantController extends BaseController {
} }
tenantProfileCache.evict(tenant.getId()); tenantProfileCache.evict(tenant.getId());
tbClusterService.onTenantChange(tenant, null); tbClusterService.onTenantChange(tenant, null);
tbClusterService.onEntityStateChange(tenant.getId(), tenant.getId(), tbClusterService.broadcastEntityStateChangeEvent(tenant.getId(), tenant.getId(),
newTenant ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); newTenant ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
return tenant; return tenant;
} catch (Exception e) { } catch (Exception e) {
@ -118,7 +118,7 @@ public class TenantController extends BaseController {
tenantService.deleteTenant(tenantId); tenantService.deleteTenant(tenantId);
tenantProfileCache.evict(tenantId); tenantProfileCache.evict(tenantId);
tbClusterService.onTenantDelete(tenant, null); tbClusterService.onTenantDelete(tenant, null);
tbClusterService.onEntityStateChange(tenantId, tenantId, ComponentLifecycleEvent.DELETED); tbClusterService.broadcastEntityStateChangeEvent(tenantId, tenantId, ComponentLifecycleEvent.DELETED);
} catch (Exception e) { } catch (Exception e) {
throw handleException(e); throw handleException(e);
} }

3
application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java

@ -34,7 +34,6 @@ import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
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.security.permission.Resource;
@ -98,7 +97,7 @@ public class TenantProfileController extends BaseController {
tenantProfile = checkNotNull(tenantProfileService.saveTenantProfile(getTenantId(), tenantProfile)); tenantProfile = checkNotNull(tenantProfileService.saveTenantProfile(getTenantId(), tenantProfile));
tenantProfileCache.put(tenantProfile); tenantProfileCache.put(tenantProfile);
tbClusterService.onTenantProfileChange(tenantProfile, null); tbClusterService.onTenantProfileChange(tenantProfile, null);
tbClusterService.onEntityStateChange(TenantId.SYS_TENANT_ID, tenantProfile.getId(), tbClusterService.broadcastEntityStateChangeEvent(TenantId.SYS_TENANT_ID, tenantProfile.getId(),
newTenantProfile ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); newTenantProfile ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
return tenantProfile; return tenantProfile;
} catch (Exception e) { } catch (Exception e) {

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

@ -200,7 +200,8 @@ public class ThingsboardInstallService {
dataUpdateService.updateData("3.2.2"); dataUpdateService.updateData("3.2.2");
systemDataLoaderService.createOAuth2Templates(); systemDataLoaderService.createOAuth2Templates();
case "3.3.0":
log.info("Upgrading ThingsBoard from version 3.3.0 to 3.3.1 ...");
log.info("Updating system data..."); log.info("Updating system data...");
systemDataLoaderService.updateSystemWidgets(); systemDataLoaderService.updateSystemWidgets();
break; break;

2
application/src/main/java/org/thingsboard/server/service/action/RuleEngineEntityActionService.java

@ -39,7 +39,7 @@ import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;

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

@ -61,7 +61,7 @@ import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.TbApplicationEventListener; import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
import org.thingsboard.server.queue.scheduler.SchedulerComponent; import org.thingsboard.server.queue.scheduler.SchedulerComponent;
import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.service.telemetry.InternalTelemetryService; import org.thingsboard.server.service.telemetry.InternalTelemetryService;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;

57
dao/src/main/java/org/thingsboard/server/dao/device/ClaimDevicesServiceImpl.java → application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java

@ -13,12 +13,14 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.dao.device; package org.thingsboard.server.service.device;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.SettableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
@ -26,6 +28,8 @@ import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager; import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
@ -37,12 +41,17 @@ import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.device.ClaimDataInfo;
import org.thingsboard.server.dao.device.ClaimDevicesService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.device.claim.ClaimData; import org.thingsboard.server.dao.device.claim.ClaimData;
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.dao.device.claim.ReclaimResult; import org.thingsboard.server.dao.device.claim.ReclaimResult;
import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.queue.util.TbCoreComponent;
import javax.annotation.Nullable;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@ -54,17 +63,22 @@ import static org.thingsboard.server.common.data.CacheConstants.CLAIM_DEVICES_CA
@Service @Service
@Slf4j @Slf4j
@TbCoreComponent
public class ClaimDevicesServiceImpl implements ClaimDevicesService { public class ClaimDevicesServiceImpl implements ClaimDevicesService {
private static final String CLAIM_ATTRIBUTE_NAME = "claimingAllowed"; private static final String CLAIM_ATTRIBUTE_NAME = "claimingAllowed";
private static final String CLAIM_DATA_ATTRIBUTE_NAME = "claimingData"; private static final String CLAIM_DATA_ATTRIBUTE_NAME = "claimingData";
private static final ObjectMapper mapper = new ObjectMapper(); private static final ObjectMapper mapper = new ObjectMapper();
@Autowired
private TbClusterService clusterService;
@Autowired @Autowired
private DeviceService deviceService; private DeviceService deviceService;
@Autowired @Autowired
private AttributesService attributesService; private AttributesService attributesService;
@Autowired @Autowired
private RuleEngineTelemetryService telemetryService;
@Autowired
private CustomerService customerService; private CustomerService customerService;
@Autowired @Autowired
private CacheManager cacheManager; private CacheManager cacheManager;
@ -144,6 +158,7 @@ public class ClaimDevicesServiceImpl implements ClaimDevicesService {
if (device.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) { if (device.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
device.setCustomerId(customerId); device.setCustomerId(customerId);
Device savedDevice = deviceService.saveDevice(device); Device savedDevice = deviceService.saveDevice(device);
clusterService.onDeviceUpdated(savedDevice, device);
return Futures.transform(removeClaimingSavedData(cache, claimData, device), result -> new ClaimResult(savedDevice, ClaimResponse.SUCCESS), MoreExecutors.directExecutor()); return Futures.transform(removeClaimingSavedData(cache, claimData, device), result -> new ClaimResult(savedDevice, ClaimResponse.SUCCESS), MoreExecutors.directExecutor());
} }
return Futures.transform(removeClaimingSavedData(cache, claimData, device), result -> new ClaimResult(null, ClaimResponse.CLAIMED), MoreExecutors.directExecutor()); return Futures.transform(removeClaimingSavedData(cache, claimData, device), result -> new ClaimResult(null, ClaimResponse.CLAIMED), MoreExecutors.directExecutor());
@ -168,14 +183,28 @@ public class ClaimDevicesServiceImpl implements ClaimDevicesService {
cacheEviction(device.getId()); cacheEviction(device.getId());
Customer unassignedCustomer = customerService.findCustomerById(tenantId, device.getCustomerId()); Customer unassignedCustomer = customerService.findCustomerById(tenantId, device.getCustomerId());
device.setCustomerId(null); device.setCustomerId(null);
deviceService.saveDevice(device); Device savedDevice = deviceService.saveDevice(device);
clusterService.onDeviceUpdated(savedDevice, device);
if (isAllowedClaimingByDefault) { if (isAllowedClaimingByDefault) {
return Futures.immediateFuture(new ReclaimResult(unassignedCustomer)); return Futures.immediateFuture(new ReclaimResult(unassignedCustomer));
} }
return Futures.transform(attributesService.save( SettableFuture<ReclaimResult> result = SettableFuture.create();
tenantId, device.getId(), DataConstants.SERVER_SCOPE, Collections.singletonList( telemetryService.saveAndNotify(
tenantId, savedDevice.getId(), DataConstants.SERVER_SCOPE, Collections.singletonList(
new BaseAttributeKvEntry(new BooleanDataEntry(CLAIM_ATTRIBUTE_NAME, true), System.currentTimeMillis()) new BaseAttributeKvEntry(new BooleanDataEntry(CLAIM_ATTRIBUTE_NAME, true), System.currentTimeMillis())
)), result -> new ReclaimResult(unassignedCustomer), MoreExecutors.directExecutor()); ),
new FutureCallback<>() {
@Override
public void onSuccess(@Nullable Void tmp) {
result.set(new ReclaimResult(unassignedCustomer));
}
@Override
public void onFailure(Throwable t) {
result.setException(t);
}
});
return result;
} }
cacheEviction(device.getId()); cacheEviction(device.getId());
return Futures.immediateFuture(new ReclaimResult(null)); return Futures.immediateFuture(new ReclaimResult(null));
@ -198,12 +227,24 @@ public class ClaimDevicesServiceImpl implements ClaimDevicesService {
return systemDurationMs; return systemDurationMs;
} }
private ListenableFuture<List<Void>> removeClaimingSavedData(Cache cache, ClaimDataInfo data, Device device) { private ListenableFuture<Void> removeClaimingSavedData(Cache cache, ClaimDataInfo data, Device device) {
if (data.isFromCache()) { if (data.isFromCache()) {
cache.evict(data.getKey()); cache.evict(data.getKey());
} }
return attributesService.removeAll(device.getTenantId(), SettableFuture<Void> result = SettableFuture.create();
device.getId(), DataConstants.SERVER_SCOPE, Arrays.asList(CLAIM_ATTRIBUTE_NAME, CLAIM_DATA_ATTRIBUTE_NAME)); telemetryService.deleteAndNotify(device.getTenantId(),
device.getId(), DataConstants.SERVER_SCOPE, Arrays.asList(CLAIM_ATTRIBUTE_NAME, CLAIM_DATA_ATTRIBUTE_NAME), new FutureCallback<>() {
@Override
public void onSuccess(@Nullable Void tmp) {
result.set(tmp);
}
@Override
public void onFailure(Throwable t) {
result.setException(t);
}
});
return result;
} }
private void cacheEviction(DeviceId deviceId) { private void cacheEviction(DeviceId deviceId) {

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

@ -24,6 +24,7 @@ import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
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;
@ -77,6 +78,9 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService {
private static final String DEVICE_PROVISION_STATE = "provisionState"; private static final String DEVICE_PROVISION_STATE = "provisionState";
private static final String PROVISIONED_STATE = "provisioned"; private static final String PROVISIONED_STATE = "provisioned";
@Autowired
TbClusterService clusterService;
@Autowired @Autowired
DeviceDao deviceDao; DeviceDao deviceDao;
@ -190,8 +194,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService {
provisionRequest.setDeviceName(newDeviceName); provisionRequest.setDeviceName(newDeviceName);
} }
Device savedDevice = deviceService.saveDevice(provisionRequest, profile); Device savedDevice = deviceService.saveDevice(provisionRequest, profile);
clusterService.onDeviceUpdated(savedDevice, null);
deviceStateService.onDeviceAdded(savedDevice);
saveProvisionStateAttribute(savedDevice).get(); saveProvisionStateAttribute(savedDevice).get();
pushDeviceCreatedEventToRuleEngine(savedDevice); pushDeviceCreatedEventToRuleEngine(savedDevice);
notify(savedDevice, provisionRequest, DataConstants.PROVISION_SUCCESS, true); notify(savedDevice, provisionRequest, DataConstants.PROVISION_SUCCESS, true);

110
application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeLicenseService.java

@ -0,0 +1,110 @@
/**
* 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.edge;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHost;
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.thingsboard.server.queue.util.TbCoreComponent;
import javax.annotation.PostConstruct;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.util.HashMap;
import java.util.Map;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
@Service
@TbCoreComponent
@Slf4j
public class DefaultEdgeLicenseService implements EdgeLicenseService {
private RestTemplate restTemplate;
private static final String EDGE_LICENSE_SERVER_ENDPOINT = "https://license.thingsboard.io";
@Value("${edges.enabled:false}")
private boolean edgesEnabled;
@PostConstruct
public void init() {
if (edgesEnabled) {
initRestTemplate();
}
}
@Override
public ResponseEntity<JsonNode> checkInstance(JsonNode request) {
return this.restTemplate.postForEntity(EDGE_LICENSE_SERVER_ENDPOINT + "/api/license/checkInstance", request, JsonNode.class);
}
@Override
public ResponseEntity<JsonNode> activateInstance(String edgeLicenseSecret, String releaseDate) {
Map<String, String> params = new HashMap<>();
params.put("licenseSecret", edgeLicenseSecret);
params.put("releaseDate", releaseDate);
return this.restTemplate.postForEntity(EDGE_LICENSE_SERVER_ENDPOINT + "/api/license/activateInstance?licenseSecret={licenseSecret}&releaseDate={releaseDate}", null, JsonNode.class, params);
}
private void initRestTemplate() {
boolean jdkHttpClientEnabled = isNotEmpty(System.getProperty("tb.proxy.jdk")) && System.getProperty("tb.proxy.jdk").equalsIgnoreCase("true");
boolean systemProxyEnabled = isNotEmpty(System.getProperty("tb.proxy.system")) && System.getProperty("tb.proxy.system").equalsIgnoreCase("true");
boolean proxyEnabled = isNotEmpty(System.getProperty("tb.proxy.host")) && isNotEmpty(System.getProperty("tb.proxy.port"));
if (jdkHttpClientEnabled) {
log.warn("Going to use plain JDK Http Client!");
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
if (proxyEnabled) {
log.warn("Going to use Proxy Server: [{}:{}]", System.getProperty("tb.proxy.host"), System.getProperty("tb.proxy.port"));
factory.setProxy(new Proxy(Proxy.Type.HTTP, InetSocketAddress.createUnresolved(System.getProperty("tb.proxy.host"), Integer.parseInt(System.getProperty("tb.proxy.port")))));
}
this.restTemplate = new RestTemplate(new SimpleClientHttpRequestFactory());
} else {
CloseableHttpClient httpClient;
HttpComponentsClientHttpRequestFactory requestFactory;
if (systemProxyEnabled) {
log.warn("Going to use System Proxy Server!");
httpClient = HttpClients.createSystem();
requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
this.restTemplate = new RestTemplate(requestFactory);
} else if (proxyEnabled) {
log.warn("Going to use Proxy Server: [{}:{}]", System.getProperty("tb.proxy.host"), System.getProperty("tb.proxy.port"));
httpClient = HttpClients.custom().setSSLHostnameVerifier(new DefaultHostnameVerifier()).setProxy(new HttpHost(System.getProperty("tb.proxy.host"), Integer.parseInt(System.getProperty("tb.proxy.port")), "https")).build();
requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
this.restTemplate = new RestTemplate(requestFactory);
} else {
httpClient = HttpClients.custom().setSSLHostnameVerifier(new DefaultHostnameVerifier()).build();
requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
this.restTemplate = new RestTemplate(requestFactory);
}
}
}
}

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

@ -16,11 +16,7 @@
package org.thingsboard.server.service.edge; package org.thingsboard.server.service.edge;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.Edge;
@ -41,8 +37,7 @@ import org.thingsboard.server.service.edge.rpc.processor.CustomerEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.EdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.EdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.EntityEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.EntityEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.RelationEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.RelationEdgeProcessor;
import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.service.queue.TbClusterService;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
@ -65,9 +60,6 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService {
@Autowired @Autowired
private TbClusterService clusterService; private TbClusterService clusterService;
@Autowired
private DbCallbackExecutorService dbCallbackExecutorService;
@Autowired @Autowired
private EdgeProcessor edgeProcessor; private EdgeProcessor edgeProcessor;
@ -123,23 +115,13 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService {
edgeEvent.setEntityId(entityId.getId()); edgeEvent.setEntityId(entityId.getId());
} }
edgeEvent.setBody(body); edgeEvent.setBody(body);
ListenableFuture<EdgeEvent> future = edgeEventService.saveAsync(edgeEvent); edgeEventService.save(edgeEvent);
Futures.addCallback(future, new FutureCallback<EdgeEvent>() { clusterService.onEdgeEventUpdate(tenantId, edgeId);
@Override
public void onSuccess(@Nullable EdgeEvent result) {
clusterService.onEdgeEventUpdate(tenantId, edgeId);
}
@Override
public void onFailure(Throwable t) {
log.warn("[{}] Can't save edge event [{}] for edge [{}]", tenantId.getId(), edgeEvent, edgeId.getId(), t);
}
}, dbCallbackExecutorService);
} }
@Override @Override
public void pushNotificationToEdge(TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg, TbCallback callback) { public void pushNotificationToEdge(TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg, TbCallback callback) {
log.trace("Pushing notification to edge {}", edgeNotificationMsg);
try { try {
TenantId tenantId = new TenantId(new UUID(edgeNotificationMsg.getTenantIdMSB(), edgeNotificationMsg.getTenantIdLSB())); TenantId tenantId = new TenantId(new UUID(edgeNotificationMsg.getTenantIdMSB(), edgeNotificationMsg.getTenantIdLSB()));
EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType()); EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType());

4
application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.service.edge; package org.thingsboard.server.service.edge;
import freemarker.template.Configuration;
import lombok.Data; import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
@ -65,6 +66,9 @@ public class EdgeContextComponent {
@Autowired @Autowired
private AdminSettingsService adminSettingsService; private AdminSettingsService adminSettingsService;
@Autowired
private Configuration freemarkerConfig;
@Autowired @Autowired
private AssetService assetService; private AssetService assetService;

26
application/src/main/java/org/thingsboard/server/service/edge/EdgeLicenseService.java

@ -0,0 +1,26 @@
/**
* 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.edge;
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.http.ResponseEntity;
public interface EdgeLicenseService {
ResponseEntity<JsonNode> checkInstance(JsonNode request);
ResponseEntity<JsonNode> activateInstance(String licenseSecret, String releaseDate);
}

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

@ -259,7 +259,7 @@ public final class EdgeGrpcSession implements Closeable {
log.error("[{}] Msg processing failed! Error msg: {}", edge.getRoutingKey(), msg.getErrorMsg()); log.error("[{}] Msg processing failed! Error msg: {}", edge.getRoutingKey(), msg.getErrorMsg());
} }
if (sessionState.getPendingMsgsMap().isEmpty()) { if (sessionState.getPendingMsgsMap().isEmpty()) {
log.debug("[{}] Pending msgs map is empty. Stopping current iteration {}", edge.getRoutingKey(), msg); log.debug("[{}] Pending msgs map is empty. Stopping current iteration", edge.getRoutingKey());
if (sessionState.getScheduledSendDownlinkTask() != null) { if (sessionState.getScheduledSendDownlinkTask() != null) {
sessionState.getScheduledSendDownlinkTask().cancel(false); sessionState.getScheduledSendDownlinkTask().cancel(false);
} }
@ -527,7 +527,7 @@ public final class EdgeGrpcSession implements Closeable {
case RULE_CHAIN_METADATA: case RULE_CHAIN_METADATA:
return ctx.getRuleChainProcessor().processRuleChainMetadataToEdge(edgeEvent, msgType); return ctx.getRuleChainProcessor().processRuleChainMetadataToEdge(edgeEvent, msgType);
case ALARM: case ALARM:
return ctx.getAlarmProcessor().processAlarmToEdge(edge, edgeEvent, msgType); return ctx.getAlarmProcessor().processAlarmToEdge(edge, edgeEvent, msgType, action);
case USER: case USER:
return ctx.getUserProcessor().processUserToEdge(edge, edgeEvent, msgType, action); return ctx.getUserProcessor().processUserToEdge(edge, edgeEvent, msgType, action);
case RELATION: case RELATION:

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

@ -50,7 +50,7 @@ public class EdgeSyncCursor {
fetchers.add(new CustomerEdgeEventFetcher()); fetchers.add(new CustomerEdgeEventFetcher());
fetchers.add(new CustomerUsersEdgeEventFetcher(ctx.getUserService(), edge.getCustomerId())); fetchers.add(new CustomerUsersEdgeEventFetcher(ctx.getUserService(), edge.getCustomerId()));
} }
fetchers.add(new AdminSettingsEdgeEventFetcher(ctx.getAdminSettingsService())); fetchers.add(new AdminSettingsEdgeEventFetcher(ctx.getAdminSettingsService(), ctx.getFreemarkerConfig()));
fetchers.add(new AssetsEdgeEventFetcher(ctx.getAssetService())); fetchers.add(new AssetsEdgeEventFetcher(ctx.getAssetService()));
fetchers.add(new DashboardsEdgeEventFetcher(ctx.getDashboardService())); fetchers.add(new DashboardsEdgeEventFetcher(ctx.getDashboardService()));
} }

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

@ -58,6 +58,8 @@ public class AlarmMsgConstructor {
} }
AlarmUpdateMsg.Builder builder = AlarmUpdateMsg.newBuilder() AlarmUpdateMsg.Builder builder = AlarmUpdateMsg.newBuilder()
.setMsgType(msgType) .setMsgType(msgType)
.setIdMSB(alarm.getId().getId().getMostSignificantBits())
.setIdLSB(alarm.getId().getId().getLeastSignificantBits())
.setName(alarm.getName()) .setName(alarm.getName())
.setType(alarm.getType()) .setType(alarm.getType())
.setOriginatorName(entityName) .setOriginatorName(entityName)

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

@ -16,10 +16,10 @@
package org.thingsboard.server.service.edge.rpc.constructor; package org.thingsboard.server.service.edge.rpc.constructor;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg; import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;

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

@ -16,9 +16,9 @@
package org.thingsboard.server.service.edge.rpc.constructor; package org.thingsboard.server.service.edge.rpc.constructor;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.gen.edge.v1.CustomerUpdateMsg; import org.thingsboard.server.gen.edge.v1.CustomerUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;

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

@ -16,10 +16,10 @@
package org.thingsboard.server.service.edge.rpc.constructor; package org.thingsboard.server.service.edge.rpc.constructor;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.gen.edge.v1.DashboardUpdateMsg; import org.thingsboard.server.gen.edge.v1.DashboardUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;

5
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceProfileMsgConstructor.java

@ -25,6 +25,8 @@ import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import java.nio.charset.StandardCharsets;
@Component @Component
@TbCoreComponent @TbCoreComponent
public class DeviceProfileMsgConstructor { public class DeviceProfileMsgConstructor {
@ -61,6 +63,9 @@ public class DeviceProfileMsgConstructor {
if (deviceProfile.getProvisionDeviceKey() != null) { if (deviceProfile.getProvisionDeviceKey() != null) {
builder.setProvisionDeviceKey(deviceProfile.getProvisionDeviceKey()); builder.setProvisionDeviceKey(deviceProfile.getProvisionDeviceKey());
} }
if (deviceProfile.getImage() != null) {
builder.setImage(ByteString.copyFrom(deviceProfile.getImage().getBytes(StandardCharsets.UTF_8)));
}
return builder.build(); return builder.build();
} }

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

@ -16,10 +16,10 @@
package org.thingsboard.server.service.edge.rpc.constructor; package org.thingsboard.server.service.edge.rpc.constructor;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.gen.edge.v1.EdgeEntityType; import org.thingsboard.server.gen.edge.v1.EdgeEntityType;
import org.thingsboard.server.gen.edge.v1.EntityViewUpdateMsg; import org.thingsboard.server.gen.edge.v1.EntityViewUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;

8
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/RelationMsgConstructor.java

@ -16,8 +16,8 @@
package org.thingsboard.server.service.edge.rpc.constructor; package org.thingsboard.server.service.edge.rpc.constructor;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.gen.edge.v1.RelationUpdateMsg; import org.thingsboard.server.gen.edge.v1.RelationUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
@ -35,8 +35,10 @@ public class RelationMsgConstructor {
.setToIdMSB(entityRelation.getTo().getId().getMostSignificantBits()) .setToIdMSB(entityRelation.getTo().getId().getMostSignificantBits())
.setToIdLSB(entityRelation.getTo().getId().getLeastSignificantBits()) .setToIdLSB(entityRelation.getTo().getId().getLeastSignificantBits())
.setToEntityType(entityRelation.getTo().getEntityType().name()) .setToEntityType(entityRelation.getTo().getEntityType().name())
.setType(entityRelation.getType()) .setType(entityRelation.getType());
.setAdditionalInfo(JacksonUtil.toString(entityRelation.getAdditionalInfo())); if (entityRelation.getAdditionalInfo() != null) {
builder.setAdditionalInfo(JacksonUtil.toString(entityRelation.getAdditionalInfo()));
}
if (entityRelation.getTypeGroup() != null) { if (entityRelation.getTypeGroup() != null) {
builder.setTypeGroup(entityRelation.getTypeGroup().name()); builder.setTypeGroup(entityRelation.getTypeGroup().name());
} }

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

@ -19,13 +19,13 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.rule.NodeConnectionInfo; import org.thingsboard.server.common.data.rule.NodeConnectionInfo;
import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainConnectionInfo; import org.thingsboard.server.common.data.rule.RuleChainConnectionInfo;
import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.gen.edge.v1.NodeConnectionInfoProto; import org.thingsboard.server.gen.edge.v1.NodeConnectionInfoProto;
import org.thingsboard.server.gen.edge.v1.RuleChainConnectionInfoProto; import org.thingsboard.server.gen.edge.v1.RuleChainConnectionInfoProto;
import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg; import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg;

5
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/UserMsgConstructor.java

@ -16,11 +16,11 @@
package org.thingsboard.server.service.edge.rpc.constructor; package org.thingsboard.server.service.edge.rpc.constructor;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.gen.edge.v1.UserCredentialsUpdateMsg; import org.thingsboard.server.gen.edge.v1.UserCredentialsUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UserUpdateMsg; import org.thingsboard.server.gen.edge.v1.UserUpdateMsg;
@ -50,9 +50,6 @@ public class UserMsgConstructor {
if (user.getAdditionalInfo() != null) { if (user.getAdditionalInfo() != null) {
builder.setAdditionalInfo(JacksonUtil.toString(user.getAdditionalInfo())); builder.setAdditionalInfo(JacksonUtil.toString(user.getAdditionalInfo()));
} }
if (user.getAdditionalInfo() != null) {
builder.setAdditionalInfo(JacksonUtil.toString(user.getAdditionalInfo()));
}
return builder.build(); return builder.build();
} }

32
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetTypeMsgConstructor.java

@ -16,10 +16,10 @@
package org.thingsboard.server.service.edge.rpc.constructor; package org.thingsboard.server.service.edge.rpc.constructor;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.id.WidgetTypeId;
import org.thingsboard.server.common.data.widget.WidgetType; import org.thingsboard.server.common.data.widget.WidgetType;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.gen.edge.v1.WidgetTypeUpdateMsg; import org.thingsboard.server.gen.edge.v1.WidgetTypeUpdateMsg;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
@ -33,21 +33,21 @@ public class WidgetTypeMsgConstructor {
.setMsgType(msgType) .setMsgType(msgType)
.setIdMSB(widgetType.getId().getId().getMostSignificantBits()) .setIdMSB(widgetType.getId().getId().getMostSignificantBits())
.setIdLSB(widgetType.getId().getId().getLeastSignificantBits()); .setIdLSB(widgetType.getId().getId().getLeastSignificantBits());
if (widgetType.getBundleAlias() != null) { if (widgetType.getBundleAlias() != null) {
builder.setBundleAlias(widgetType.getBundleAlias()); builder.setBundleAlias(widgetType.getBundleAlias());
} }
if (widgetType.getAlias() != null) { if (widgetType.getAlias() != null) {
builder.setAlias(widgetType.getAlias()); builder.setAlias(widgetType.getAlias());
} }
if (widgetType.getName() != null) { if (widgetType.getName() != null) {
builder.setName(widgetType.getName()); builder.setName(widgetType.getName());
} }
if (widgetType.getDescriptor() != null) { if (widgetType.getDescriptor() != null) {
builder.setDescriptorJson(JacksonUtil.toString(widgetType.getDescriptor())); builder.setDescriptorJson(JacksonUtil.toString(widgetType.getDescriptor()));
} }
if (widgetType.getTenantId().equals(TenantId.SYS_TENANT_ID)) { if (widgetType.getTenantId().equals(TenantId.SYS_TENANT_ID)) {
builder.setIsSystem(true); builder.setIsSystem(true);
} }
return builder.build(); return builder.build();
} }

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetsBundleMsgConstructor.java

@ -40,6 +40,9 @@ public class WidgetsBundleMsgConstructor {
if (widgetsBundle.getImage() != null) { if (widgetsBundle.getImage() != null) {
builder.setImage(ByteString.copyFrom(widgetsBundle.getImage().getBytes(StandardCharsets.UTF_8))); builder.setImage(ByteString.copyFrom(widgetsBundle.getImage().getBytes(StandardCharsets.UTF_8)));
} }
if (widgetsBundle.getDescription() != null) {
builder.setDescription(widgetsBundle.getDescription());
}
if (widgetsBundle.getTenantId().equals(TenantId.SYS_TENANT_ID)) { if (widgetsBundle.getTenantId().equals(TenantId.SYS_TENANT_ID)) {
builder.setIsSystem(true); builder.setIsSystem(true);
} }

68
application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java

@ -19,12 +19,12 @@ import com.datastax.oss.driver.api.core.uuid.Uuids;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import freemarker.template.Configuration;
import freemarker.template.Template;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.text.WordUtils; import org.apache.commons.lang3.text.WordUtils;
import org.springframework.core.io.DefaultResourceLoader;
import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEvent;
@ -37,9 +37,8 @@ import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.dao.settings.AdminSettingsService;
import org.thingsboard.server.service.edge.rpc.EdgeEventUtils; import org.thingsboard.server.service.edge.rpc.EdgeEventUtils;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -53,6 +52,23 @@ public class AdminSettingsEdgeEventFetcher implements EdgeEventFetcher {
private static final ObjectMapper mapper = new ObjectMapper(); private static final ObjectMapper mapper = new ObjectMapper();
private final AdminSettingsService adminSettingsService; private final AdminSettingsService adminSettingsService;
private final Configuration freemarkerConfig;
private static Pattern startPattern = Pattern.compile("<div class=\"content\".*?>");
private static Pattern endPattern = Pattern.compile("<div class=\"footer\".*?>");
private static List<String> templatesNames = Arrays.asList(
"account.activated.ftl",
"account.lockout.ftl",
"activation.ftl",
"password.was.reset.ftl",
"reset.password.ftl",
"test.ftl");
// TODO: fix format of next templates
// "state.disabled.ftl",
// "state.enabled.ftl",
// "state.warning.ftl",
@Override @Override
public PageLink getPageLink(int pageSize) { public PageLink getPageLink(int pageSize) {
@ -85,23 +101,16 @@ public class AdminSettingsEdgeEventFetcher implements EdgeEventFetcher {
private AdminSettings loadMailTemplates() throws Exception { private AdminSettings loadMailTemplates() throws Exception {
Map<String, Object> mailTemplates = new HashMap<>(); Map<String, Object> mailTemplates = new HashMap<>();
Pattern startPattern = Pattern.compile("<div class=\"content\".*?>"); for (String templatesName : templatesNames) {
Pattern endPattern = Pattern.compile("<div class=\"footer\".*?>"); Template template = freemarkerConfig.getTemplate(templatesName);
File[] files = new DefaultResourceLoader().getResource("classpath:/templates/").getFile().listFiles(); if (template != null) {
for (File file : files) { String name = validateName(template.getName());
Map<String, String> mailTemplate = new HashMap<>(); Map<String, String> mailTemplate = getMailTemplateFromFile(template.getRootTreeNode().toString());
String name = validateName(file.getName()); if (mailTemplate != null) {
String stringTemplate = FileUtils.readFileToString(file, StandardCharsets.UTF_8); mailTemplates.put(name, mailTemplate);
Matcher start = startPattern.matcher(stringTemplate); } else {
Matcher end = endPattern.matcher(stringTemplate); log.error("Can't load mail template from file {}", template.getName());
if (start.find() && end.find()) { }
String body = StringUtils.substringBetween(stringTemplate, start.group(), end.group()).replaceAll("\t", "");
String subject = StringUtils.substringBetween(body, "<h2>", "</h2>");
mailTemplate.put("subject", subject);
mailTemplate.put("body", body);
mailTemplates.put(name, mailTemplate);
} else {
log.error("Can't load mail template from file {}", file.getName());
} }
} }
AdminSettings adminSettings = new AdminSettings(); AdminSettings adminSettings = new AdminSettings();
@ -111,9 +120,24 @@ public class AdminSettingsEdgeEventFetcher implements EdgeEventFetcher {
return adminSettings; return adminSettings;
} }
private Map<String, String> getMailTemplateFromFile(String stringTemplate) {
Map<String, String> mailTemplate = new HashMap<>();
Matcher start = startPattern.matcher(stringTemplate);
Matcher end = endPattern.matcher(stringTemplate);
if (start.find() && end.find()) {
String body = StringUtils.substringBetween(stringTemplate, start.group(), end.group()).replaceAll("\t", "");
String subject = StringUtils.substringBetween(body, "<h2>", "</h2>");
mailTemplate.put("subject", subject);
mailTemplate.put("body", body);
} else {
return null;
}
return mailTemplate;
}
private String validateName(String name) throws Exception { private String validateName(String name) throws Exception {
StringBuilder nameBuilder = new StringBuilder(); StringBuilder nameBuilder = new StringBuilder();
name = name.replace(".vm", ""); name = name.replace(".ftl", "");
String[] nameParts = name.split("\\."); String[] nameParts = name.split("\\.");
if (nameParts.length >= 1) { if (nameParts.length >= 1) {
nameBuilder.append(nameParts[0]); nameBuilder.append(nameParts[0]);

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

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.service.edge.rpc.processor; package org.thingsboard.server.service.edge.rpc.processor;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
@ -54,6 +55,7 @@ public class AlarmEdgeProcessor extends BaseEdgeProcessor {
EntityId originatorId = getAlarmOriginator(tenantId, alarmUpdateMsg.getOriginatorName(), EntityId originatorId = getAlarmOriginator(tenantId, alarmUpdateMsg.getOriginatorName(),
EntityType.valueOf(alarmUpdateMsg.getOriginatorType())); EntityType.valueOf(alarmUpdateMsg.getOriginatorType()));
if (originatorId == null) { if (originatorId == null) {
log.warn("Originator not found for the alarm msg {}", alarmUpdateMsg);
return Futures.immediateFuture(null); return Futures.immediateFuture(null);
} }
try { try {
@ -113,59 +115,84 @@ public class AlarmEdgeProcessor extends BaseEdgeProcessor {
} }
} }
public DownlinkMsg processAlarmToEdge(Edge edge, EdgeEvent edgeEvent, UpdateMsgType msgType) { public DownlinkMsg processAlarmToEdge(Edge edge, EdgeEvent edgeEvent, UpdateMsgType msgType, EdgeEventActionType action) {
AlarmId alarmId = new AlarmId(edgeEvent.getEntityId());
DownlinkMsg downlinkMsg = null; DownlinkMsg downlinkMsg = null;
try { switch (action) {
AlarmId alarmId = new AlarmId(edgeEvent.getEntityId()); case ADDED:
Alarm alarm = alarmService.findAlarmByIdAsync(edgeEvent.getTenantId(), alarmId).get(); case UPDATED:
if (alarm != null) { case ALARM_ACK:
case ALARM_CLEAR:
try {
Alarm alarm = alarmService.findAlarmByIdAsync(edgeEvent.getTenantId(), alarmId).get();
if (alarm != null) {
downlinkMsg = DownlinkMsg.newBuilder()
.setDownlinkMsgId(EdgeUtils.nextPositiveInt())
.addAlarmUpdateMsg(alarmMsgConstructor.constructAlarmUpdatedMsg(edge.getTenantId(), msgType, alarm))
.build();
}
} catch (Exception e) {
log.error("Can't process alarm msg [{}] [{}]", edgeEvent, msgType, e);
}
break;
case DELETED:
Alarm alarm = mapper.convertValue(edgeEvent.getBody(), Alarm.class);
AlarmUpdateMsg alarmUpdateMsg =
alarmMsgConstructor.constructAlarmUpdatedMsg(edge.getTenantId(), msgType, alarm);
downlinkMsg = DownlinkMsg.newBuilder() downlinkMsg = DownlinkMsg.newBuilder()
.setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .setDownlinkMsgId(EdgeUtils.nextPositiveInt())
.addAlarmUpdateMsg(alarmMsgConstructor.constructAlarmUpdatedMsg(edge.getTenantId(), msgType, alarm)) .addAlarmUpdateMsg(alarmUpdateMsg)
.build(); .build();
} break;
} catch (Exception e) {
log.error("Can't process alarm msg [{}] [{}]", edgeEvent, msgType, e);
} }
return downlinkMsg; return downlinkMsg;
} }
public void processAlarmNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { public void processAlarmNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) throws JsonProcessingException {
EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction());
AlarmId alarmId = new AlarmId(new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB())); AlarmId alarmId = new AlarmId(new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB()));
ListenableFuture<Alarm> alarmFuture = alarmService.findAlarmByIdAsync(tenantId, alarmId); switch (actionType) {
Futures.addCallback(alarmFuture, new FutureCallback<Alarm>() { case DELETED:
@Override EdgeId edgeId = new EdgeId(new UUID(edgeNotificationMsg.getEdgeIdMSB(), edgeNotificationMsg.getEdgeIdLSB()));
public void onSuccess(@Nullable Alarm alarm) { Alarm alarm = mapper.readValue(edgeNotificationMsg.getBody(), Alarm.class);
if (alarm != null) { saveEdgeEvent(tenantId, edgeId, EdgeEventType.ALARM, actionType, alarmId, mapper.valueToTree(alarm));
EdgeEventType type = EdgeUtils.getEdgeEventTypeByEntityType(alarm.getOriginator().getEntityType()); break;
if (type != null) { default:
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); ListenableFuture<Alarm> alarmFuture = alarmService.findAlarmByIdAsync(tenantId, alarmId);
PageData<EdgeId> pageData; Futures.addCallback(alarmFuture, new FutureCallback<Alarm>() {
do { @Override
pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, alarm.getOriginator(), pageLink); public void onSuccess(@Nullable Alarm alarm) {
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { if (alarm != null) {
for (EdgeId edgeId : pageData.getData()) { EdgeEventType type = EdgeUtils.getEdgeEventTypeByEntityType(alarm.getOriginator().getEntityType());
saveEdgeEvent(tenantId, if (type != null) {
edgeId, PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
EdgeEventType.ALARM, PageData<EdgeId> pageData;
EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()), do {
alarmId, pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, alarm.getOriginator(), pageLink);
null); if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
} for (EdgeId edgeId : pageData.getData()) {
if (pageData.hasNext()) { saveEdgeEvent(tenantId,
pageLink = pageLink.nextPageLink(); edgeId,
} EdgeEventType.ALARM,
EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()),
alarmId,
null);
}
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
}
} while (pageData != null && pageData.hasNext());
} }
} while (pageData != null && pageData.hasNext()); }
} }
}
}
@Override @Override
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
log.warn("[{}] can't find alarm by id [{}] {}", tenantId.getId(), alarmId.getId(), t); log.warn("[{}] can't find alarm by id [{}] {}", tenantId.getId(), alarmId.getId(), t);
} }
}, dbCallbackExecutorService); }, dbCallbackExecutorService);
}
} }
} }

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

@ -17,11 +17,7 @@ package org.thingsboard.server.service.edge.rpc.processor;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.server.common.data.HasCustomerId; import org.thingsboard.server.common.data.HasCustomerId;
import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.Edge;
@ -66,7 +62,7 @@ import org.thingsboard.server.service.edge.rpc.constructor.WidgetTypeMsgConstruc
import org.thingsboard.server.service.edge.rpc.constructor.WidgetsBundleMsgConstructor; import org.thingsboard.server.service.edge.rpc.constructor.WidgetsBundleMsgConstructor;
import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.service.state.DeviceStateService; import org.thingsboard.server.service.state.DeviceStateService;
@Slf4j @Slf4j
@ -178,12 +174,12 @@ public abstract class BaseEdgeProcessor {
@Autowired @Autowired
protected DbCallbackExecutorService dbCallbackExecutorService; protected DbCallbackExecutorService dbCallbackExecutorService;
protected ListenableFuture<EdgeEvent> saveEdgeEvent(TenantId tenantId, protected void saveEdgeEvent(TenantId tenantId,
EdgeId edgeId, EdgeId edgeId,
EdgeEventType type, EdgeEventType type,
EdgeEventActionType action, EdgeEventActionType action,
EntityId entityId, EntityId entityId,
JsonNode body) { JsonNode body) {
log.debug("Pushing event to edge queue. tenantId [{}], edgeId [{}], type[{}], " + log.debug("Pushing event to edge queue. tenantId [{}], edgeId [{}], type[{}], " +
"action [{}], entityId [{}], body [{}]", "action [{}], entityId [{}], body [{}]",
tenantId, edgeId, type, action, entityId, body); tenantId, edgeId, type, action, entityId, body);
@ -197,19 +193,8 @@ public abstract class BaseEdgeProcessor {
edgeEvent.setEntityId(entityId.getId()); edgeEvent.setEntityId(entityId.getId());
} }
edgeEvent.setBody(body); edgeEvent.setBody(body);
ListenableFuture<EdgeEvent> future = edgeEventService.saveAsync(edgeEvent); edgeEventService.save(edgeEvent);
Futures.addCallback(future, new FutureCallback<EdgeEvent>() { tbClusterService.onEdgeEventUpdate(tenantId, edgeId);
@Override
public void onSuccess(@Nullable EdgeEvent result) {
tbClusterService.onEdgeEventUpdate(tenantId, edgeId);
}
@Override
public void onFailure(Throwable t) {
log.warn("[{}] Can't save edge event [{}] for edge [{}]", tenantId.getId(), edgeEvent, edgeId.getId(), t);
}
}, dbCallbackExecutorService);
return future;
} }
protected CustomerId getCustomerIdIfEdgeAssignedToCustomer(HasCustomerId hasCustomerIdEntity, Edge edge) { protected CustomerId getCustomerIdIfEdgeAssignedToCustomer(HasCustomerId hasCustomerIdEntity, Edge edge) {

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

@ -18,7 +18,6 @@ package org.thingsboard.server.service.edge.rpc.processor;
import com.datastax.oss.driver.api.core.uuid.Uuids; import com.datastax.oss.driver.api.core.uuid.Uuids;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture; import com.google.common.util.concurrent.SettableFuture;
@ -27,7 +26,7 @@ import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.RpcError; import org.thingsboard.server.common.data.rpc.RpcError;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
@ -60,7 +59,7 @@ import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.TbQueueCallback;
import org.thingsboard.server.queue.TbQueueMsgMetadata; import org.thingsboard.server.queue.TbQueueMsgMetadata;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.rpc.FromDeviceRpcResponse; import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse;
import org.thingsboard.server.service.rpc.FromDeviceRpcResponseActorMsg; import org.thingsboard.server.service.rpc.FromDeviceRpcResponseActorMsg;
import java.util.UUID; import java.util.UUID;
@ -105,19 +104,8 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor {
Device newDevice = createDevice(tenantId, edge, deviceUpdateMsg, newDeviceName); Device newDevice = createDevice(tenantId, edge, deviceUpdateMsg, newDeviceName);
ObjectNode body = mapper.createObjectNode(); ObjectNode body = mapper.createObjectNode();
body.put("conflictName", deviceName); body.put("conflictName", deviceName);
ListenableFuture<EdgeEvent> future = saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.ENTITY_MERGE_REQUEST, newDevice.getId(), body);
saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.ENTITY_MERGE_REQUEST, newDevice.getId(), body); saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.CREDENTIALS_REQUEST, newDevice.getId(), null);
Futures.addCallback(future, new FutureCallback<>() {
@Override
public void onSuccess(EdgeEvent edgeEvent) {
saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.CREDENTIALS_REQUEST, newDevice.getId(), null);
}
@Override
public void onFailure(Throwable t) {
log.error("[{}] Failed to save ENTITY_MERGE_REQUEST edge event [{}][{}]", tenantId, deviceUpdateMsg, edge.getId(), t);
}
}, dbCallbackExecutorService);
} }
} while (pageData != null && pageData.hasNext()); } while (pageData != null && pageData.hasNext());
} else { } else {
@ -155,7 +143,9 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor {
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, device.getId()); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, device.getId());
deviceCredentials.setCredentialsType(DeviceCredentialsType.valueOf(deviceCredentialsUpdateMsg.getCredentialsType())); deviceCredentials.setCredentialsType(DeviceCredentialsType.valueOf(deviceCredentialsUpdateMsg.getCredentialsType()));
deviceCredentials.setCredentialsId(deviceCredentialsUpdateMsg.getCredentialsId()); deviceCredentials.setCredentialsId(deviceCredentialsUpdateMsg.getCredentialsId());
deviceCredentials.setCredentialsValue(deviceCredentialsUpdateMsg.getCredentialsValue()); if (deviceCredentialsUpdateMsg.hasCredentialsValue()) {
deviceCredentials.setCredentialsValue(deviceCredentialsUpdateMsg.getCredentialsValue());
}
deviceCredentialsService.updateDeviceCredentials(tenantId, deviceCredentials); deviceCredentialsService.updateDeviceCredentials(tenantId, deviceCredentials);
} catch (Exception e) { } catch (Exception e) {
log.error("Can't update device credentials for device [{}], deviceCredentialsUpdateMsg [{}]", device.getName(), deviceCredentialsUpdateMsg, e); log.error("Can't update device credentials for device [{}], deviceCredentialsUpdateMsg [{}]", device.getName(), deviceCredentialsUpdateMsg, e);
@ -173,14 +163,20 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor {
if (device != null) { if (device != null) {
device.setName(deviceUpdateMsg.getName()); device.setName(deviceUpdateMsg.getName());
device.setType(deviceUpdateMsg.getType()); device.setType(deviceUpdateMsg.getType());
device.setLabel(deviceUpdateMsg.getLabel()); if (deviceUpdateMsg.hasLabel()) {
device.setAdditionalInfo(JacksonUtil.toJsonNode(deviceUpdateMsg.getAdditionalInfo())); device.setLabel(deviceUpdateMsg.getLabel());
if (deviceUpdateMsg.getDeviceProfileIdMSB() != 0 && deviceUpdateMsg.getDeviceProfileIdLSB() != 0) { }
if (deviceUpdateMsg.hasAdditionalInfo()) {
device.setAdditionalInfo(JacksonUtil.toJsonNode(deviceUpdateMsg.getAdditionalInfo()));
}
if (deviceUpdateMsg.hasDeviceProfileIdMSB() && deviceUpdateMsg.hasDeviceProfileIdLSB()) {
DeviceProfileId deviceProfileId = new DeviceProfileId( DeviceProfileId deviceProfileId = new DeviceProfileId(
new UUID(deviceUpdateMsg.getDeviceProfileIdMSB(), deviceUpdateMsg.getDeviceProfileIdLSB())); new UUID(deviceUpdateMsg.getDeviceProfileIdMSB(),
deviceUpdateMsg.getDeviceProfileIdLSB()));
device.setDeviceProfileId(deviceProfileId); device.setDeviceProfileId(deviceProfileId);
} }
deviceService.saveDevice(device); Device savedDevice = deviceService.saveDevice(device);
tbClusterService.onDeviceUpdated(savedDevice, device);
saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.CREDENTIALS_REQUEST, deviceId, null); saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.CREDENTIALS_REQUEST, deviceId, null);
} else { } else {
log.warn("[{}] can't find device [{}], edge [{}]", tenantId, deviceUpdateMsg, edge.getId()); log.warn("[{}] can't find device [{}], edge [{}]", tenantId, deviceUpdateMsg, edge.getId());
@ -206,22 +202,26 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor {
device.setCustomerId(getCustomerId(edge)); device.setCustomerId(getCustomerId(edge));
device.setName(deviceName); device.setName(deviceName);
device.setType(deviceUpdateMsg.getType()); device.setType(deviceUpdateMsg.getType());
device.setLabel(deviceUpdateMsg.getLabel()); if (deviceUpdateMsg.hasLabel()) {
device.setAdditionalInfo(JacksonUtil.toJsonNode(deviceUpdateMsg.getAdditionalInfo())); device.setLabel(deviceUpdateMsg.getLabel());
if (deviceUpdateMsg.getDeviceProfileIdMSB() != 0 && deviceUpdateMsg.getDeviceProfileIdLSB() != 0) { }
if (deviceUpdateMsg.hasAdditionalInfo()) {
device.setAdditionalInfo(JacksonUtil.toJsonNode(deviceUpdateMsg.getAdditionalInfo()));
}
if (deviceUpdateMsg.hasDeviceProfileIdMSB() && deviceUpdateMsg.hasDeviceProfileIdLSB()) {
DeviceProfileId deviceProfileId = new DeviceProfileId( DeviceProfileId deviceProfileId = new DeviceProfileId(
new UUID(deviceUpdateMsg.getDeviceProfileIdMSB(), deviceUpdateMsg.getDeviceProfileIdLSB())); new UUID(deviceUpdateMsg.getDeviceProfileIdMSB(),
deviceUpdateMsg.getDeviceProfileIdLSB()));
device.setDeviceProfileId(deviceProfileId); device.setDeviceProfileId(deviceProfileId);
} }
Device savedDevice = deviceService.saveDevice(device, false); Device savedDevice = deviceService.saveDevice(device, false);
tbClusterService.onDeviceUpdated(savedDevice, created ? null : device, false);
if (created) { if (created) {
DeviceCredentials deviceCredentials = new DeviceCredentials(); DeviceCredentials deviceCredentials = new DeviceCredentials();
deviceCredentials.setDeviceId(new DeviceId(savedDevice.getUuidId())); deviceCredentials.setDeviceId(new DeviceId(savedDevice.getUuidId()));
deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN); deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN);
deviceCredentials.setCredentialsId(org.apache.commons.lang3.RandomStringUtils.randomAlphanumeric(20)); deviceCredentials.setCredentialsId(org.apache.commons.lang3.RandomStringUtils.randomAlphanumeric(20));
deviceCredentialsService.createDeviceCredentials(device.getTenantId(), deviceCredentials); deviceCredentialsService.createDeviceCredentials(device.getTenantId(), deviceCredentials);
deviceStateService.onDeviceAdded(savedDevice);
} }
createRelationFromEdge(tenantId, edge.getId(), device.getId()); createRelationFromEdge(tenantId, edge.getId(), device.getId());
pushDeviceCreatedEventToRuleEngine(tenantId, edge, device); pushDeviceCreatedEventToRuleEngine(tenantId, edge, device);

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

@ -72,7 +72,9 @@ public class RelationEdgeProcessor extends BaseEdgeProcessor {
entityRelation.setTo(toId); entityRelation.setTo(toId);
entityRelation.setType(relationUpdateMsg.getType()); entityRelation.setType(relationUpdateMsg.getType());
entityRelation.setTypeGroup(RelationTypeGroup.valueOf(relationUpdateMsg.getTypeGroup())); if (relationUpdateMsg.hasTypeGroup()) {
entityRelation.setTypeGroup(RelationTypeGroup.valueOf(relationUpdateMsg.getTypeGroup()));
}
entityRelation.setAdditionalInfo(mapper.readTree(relationUpdateMsg.getAdditionalInfo())); entityRelation.setAdditionalInfo(mapper.readTree(relationUpdateMsg.getAdditionalInfo()));
switch (relationUpdateMsg.getMsgType()) { switch (relationUpdateMsg.getMsgType()) {
case ENTITY_CREATED_RPC_MESSAGE: case ENTITY_CREATED_RPC_MESSAGE:

161
application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java

@ -73,7 +73,7 @@ import org.thingsboard.server.gen.edge.v1.UserCredentialsRequestMsg;
import org.thingsboard.server.gen.edge.v1.WidgetBundleTypesRequestMsg; import org.thingsboard.server.gen.edge.v1.WidgetBundleTypesRequestMsg;
import org.thingsboard.server.service.edge.rpc.EdgeEventUtils; import org.thingsboard.server.service.edge.rpc.EdgeEventUtils;
import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
@ -122,26 +122,13 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService {
@Override @Override
public ListenableFuture<Void> processRuleChainMetadataRequestMsg(TenantId tenantId, Edge edge, RuleChainMetadataRequestMsg ruleChainMetadataRequestMsg) { public ListenableFuture<Void> processRuleChainMetadataRequestMsg(TenantId tenantId, Edge edge, RuleChainMetadataRequestMsg ruleChainMetadataRequestMsg) {
log.trace("[{}] processRuleChainMetadataRequestMsg [{}][{}]", tenantId, edge.getName(), ruleChainMetadataRequestMsg); log.trace("[{}] processRuleChainMetadataRequestMsg [{}][{}]", tenantId, edge.getName(), ruleChainMetadataRequestMsg);
SettableFuture<Void> futureToSet = SettableFuture.create();
if (ruleChainMetadataRequestMsg.getRuleChainIdMSB() != 0 && ruleChainMetadataRequestMsg.getRuleChainIdLSB() != 0) { if (ruleChainMetadataRequestMsg.getRuleChainIdMSB() != 0 && ruleChainMetadataRequestMsg.getRuleChainIdLSB() != 0) {
RuleChainId ruleChainId = RuleChainId ruleChainId =
new RuleChainId(new UUID(ruleChainMetadataRequestMsg.getRuleChainIdMSB(), ruleChainMetadataRequestMsg.getRuleChainIdLSB())); new RuleChainId(new UUID(ruleChainMetadataRequestMsg.getRuleChainIdMSB(), ruleChainMetadataRequestMsg.getRuleChainIdLSB()));
ListenableFuture<EdgeEvent> future = saveEdgeEvent(tenantId, edge.getId(), saveEdgeEvent(tenantId, edge.getId(),
EdgeEventType.RULE_CHAIN_METADATA, EdgeEventActionType.ADDED, ruleChainId, null); EdgeEventType.RULE_CHAIN_METADATA, EdgeEventActionType.ADDED, ruleChainId, null);
Futures.addCallback(future, new FutureCallback<EdgeEvent>() {
@Override
public void onSuccess(@Nullable EdgeEvent result) {
futureToSet.set(null);
}
@Override
public void onFailure(Throwable t) {
log.error("Can't save edge event [{}]", ruleChainMetadataRequestMsg, t);
futureToSet.setException(t);
}
}, dbCallbackExecutorService);
} }
return futureToSet; return Futures.immediateFuture(null);
} }
@Override @Override
@ -154,8 +141,8 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService {
if (type != null) { if (type != null) {
SettableFuture<Void> futureToSet = SettableFuture.create(); SettableFuture<Void> futureToSet = SettableFuture.create();
String scope = attributesRequestMsg.getScope(); String scope = attributesRequestMsg.getScope();
ListenableFuture<List<AttributeKvEntry>> ssAttrFuture = attributesService.findAll(tenantId, entityId, scope); ListenableFuture<List<AttributeKvEntry>> findAttrFuture = attributesService.findAll(tenantId, entityId, scope);
Futures.addCallback(ssAttrFuture, new FutureCallback<List<AttributeKvEntry>>() { Futures.addCallback(findAttrFuture, new FutureCallback<List<AttributeKvEntry>>() {
@Override @Override
public void onSuccess(@Nullable List<AttributeKvEntry> ssAttributes) { public void onSuccess(@Nullable List<AttributeKvEntry> ssAttributes) {
if (ssAttributes != null && !ssAttributes.isEmpty()) { if (ssAttributes != null && !ssAttributes.isEmpty()) {
@ -184,8 +171,9 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService {
entityId, entityId,
body); body);
} catch (Exception e) { } catch (Exception e) {
log.error("[{}] Failed to send attribute updates to the edge", edge.getName(), e); log.error("[{}] Failed to save attribute updates to the edge", edge.getName(), e);
throw new RuntimeException("[" + edge.getName() + "] Failed to send attribute updates to the edge", e); futureToSet.setException(new RuntimeException("[" + edge.getName() + "] Failed to send attribute updates to the edge", e));
return;
} }
} else { } else {
log.trace("[{}][{}] No attributes found for entity {} [{}]", tenantId, log.trace("[{}][{}] No attributes found for entity {} [{}]", tenantId,
@ -198,7 +186,7 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService {
@Override @Override
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
log.error("Can't save attributes [{}]", attributesRequestMsg, t); log.error("Can't find attributes [{}]", attributesRequestMsg, t);
futureToSet.setException(t); futureToSet.setException(t);
} }
}, dbCallbackExecutorService); }, dbCallbackExecutorService);
@ -273,82 +261,39 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService {
@Override @Override
public ListenableFuture<Void> processDeviceCredentialsRequestMsg(TenantId tenantId, Edge edge, DeviceCredentialsRequestMsg deviceCredentialsRequestMsg) { public ListenableFuture<Void> processDeviceCredentialsRequestMsg(TenantId tenantId, Edge edge, DeviceCredentialsRequestMsg deviceCredentialsRequestMsg) {
log.trace("[{}] processDeviceCredentialsRequestMsg [{}][{}]", tenantId, edge.getName(), deviceCredentialsRequestMsg); log.trace("[{}] processDeviceCredentialsRequestMsg [{}][{}]", tenantId, edge.getName(), deviceCredentialsRequestMsg);
SettableFuture<Void> futureToSet = SettableFuture.create();
if (deviceCredentialsRequestMsg.getDeviceIdMSB() != 0 && deviceCredentialsRequestMsg.getDeviceIdLSB() != 0) { if (deviceCredentialsRequestMsg.getDeviceIdMSB() != 0 && deviceCredentialsRequestMsg.getDeviceIdLSB() != 0) {
DeviceId deviceId = new DeviceId(new UUID(deviceCredentialsRequestMsg.getDeviceIdMSB(), deviceCredentialsRequestMsg.getDeviceIdLSB())); DeviceId deviceId = new DeviceId(new UUID(deviceCredentialsRequestMsg.getDeviceIdMSB(), deviceCredentialsRequestMsg.getDeviceIdLSB()));
ListenableFuture<EdgeEvent> future = saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE,
EdgeEventActionType.CREDENTIALS_UPDATED, deviceId, null); EdgeEventActionType.CREDENTIALS_UPDATED, deviceId, null);
Futures.addCallback(future, new FutureCallback<EdgeEvent>() {
@Override
public void onSuccess(@Nullable EdgeEvent result) {
futureToSet.set(null);
}
@Override
public void onFailure(Throwable t) {
log.error("Can't save edge event [{}]", deviceCredentialsRequestMsg, t);
futureToSet.setException(t);
}
}, dbCallbackExecutorService);
} }
return futureToSet; return Futures.immediateFuture(null);
} }
@Override @Override
public ListenableFuture<Void> processUserCredentialsRequestMsg(TenantId tenantId, Edge edge, UserCredentialsRequestMsg userCredentialsRequestMsg) { public ListenableFuture<Void> processUserCredentialsRequestMsg(TenantId tenantId, Edge edge, UserCredentialsRequestMsg userCredentialsRequestMsg) {
log.trace("[{}] processUserCredentialsRequestMsg [{}][{}]", tenantId, edge.getName(), userCredentialsRequestMsg); log.trace("[{}] processUserCredentialsRequestMsg [{}][{}]", tenantId, edge.getName(), userCredentialsRequestMsg);
SettableFuture<Void> futureToSet = SettableFuture.create();
if (userCredentialsRequestMsg.getUserIdMSB() != 0 && userCredentialsRequestMsg.getUserIdLSB() != 0) { if (userCredentialsRequestMsg.getUserIdMSB() != 0 && userCredentialsRequestMsg.getUserIdLSB() != 0) {
UserId userId = new UserId(new UUID(userCredentialsRequestMsg.getUserIdMSB(), userCredentialsRequestMsg.getUserIdLSB())); UserId userId = new UserId(new UUID(userCredentialsRequestMsg.getUserIdMSB(), userCredentialsRequestMsg.getUserIdLSB()));
ListenableFuture<EdgeEvent> future = saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.USER, saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.USER,
EdgeEventActionType.CREDENTIALS_UPDATED, userId, null); EdgeEventActionType.CREDENTIALS_UPDATED, userId, null);
Futures.addCallback(future, new FutureCallback<>() {
@Override
public void onSuccess(@Nullable EdgeEvent result) {
futureToSet.set(null);
}
@Override
public void onFailure(Throwable t) {
log.error("Can't save edge event [{}]", userCredentialsRequestMsg, t);
futureToSet.setException(t);
}
}, dbCallbackExecutorService);
} }
return futureToSet; return Futures.immediateFuture(null);
} }
@Override @Override
public ListenableFuture<Void> processDeviceProfileDevicesRequestMsg(TenantId tenantId, Edge edge, DeviceProfileDevicesRequestMsg deviceProfileDevicesRequestMsg) { public ListenableFuture<Void> processDeviceProfileDevicesRequestMsg(TenantId tenantId, Edge edge, DeviceProfileDevicesRequestMsg deviceProfileDevicesRequestMsg) {
log.trace("[{}] processDeviceProfileDevicesRequestMsg [{}][{}]", tenantId, edge.getName(), deviceProfileDevicesRequestMsg); log.trace("[{}] processDeviceProfileDevicesRequestMsg [{}][{}]", tenantId, edge.getName(), deviceProfileDevicesRequestMsg);
SettableFuture<Void> futureToSet = SettableFuture.create();
if (deviceProfileDevicesRequestMsg.getDeviceProfileIdMSB() != 0 && deviceProfileDevicesRequestMsg.getDeviceProfileIdLSB() != 0) { if (deviceProfileDevicesRequestMsg.getDeviceProfileIdMSB() != 0 && deviceProfileDevicesRequestMsg.getDeviceProfileIdLSB() != 0) {
DeviceProfileId deviceProfileId = new DeviceProfileId(new UUID(deviceProfileDevicesRequestMsg.getDeviceProfileIdMSB(), deviceProfileDevicesRequestMsg.getDeviceProfileIdLSB())); DeviceProfileId deviceProfileId = new DeviceProfileId(new UUID(deviceProfileDevicesRequestMsg.getDeviceProfileIdMSB(), deviceProfileDevicesRequestMsg.getDeviceProfileIdLSB()));
DeviceProfile deviceProfileById = deviceProfileService.findDeviceProfileById(tenantId, deviceProfileId); DeviceProfile deviceProfileById = deviceProfileService.findDeviceProfileById(tenantId, deviceProfileId);
List<ListenableFuture<EdgeEvent>> futures;
if (deviceProfileById != null) { if (deviceProfileById != null) {
futures = syncDevices(tenantId, edge, deviceProfileById.getName()); syncDevices(tenantId, edge, deviceProfileById.getName());
} else {
futures = new ArrayList<>();
} }
Futures.addCallback(Futures.allAsList(futures), new FutureCallback<>() {
@Override
public void onSuccess(@Nullable List<EdgeEvent> result) {
futureToSet.set(null);
}
@Override
public void onFailure(Throwable t) {
log.error("Can't sync devices by device profile [{}]", deviceProfileDevicesRequestMsg, t);
futureToSet.setException(t);
}
}, dbCallbackExecutorService);
} }
return futureToSet; return Futures.immediateFuture(null);
} }
private List<ListenableFuture<EdgeEvent>> syncDevices(TenantId tenantId, Edge edge, String deviceType) { private void syncDevices(TenantId tenantId, Edge edge, String deviceType) {
List<ListenableFuture<EdgeEvent>> futures = new ArrayList<>();
log.trace("[{}] syncDevices [{}][{}]", tenantId, edge.getName(), deviceType); log.trace("[{}] syncDevices [{}][{}]", tenantId, edge.getName(), deviceType);
try { try {
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
@ -358,7 +303,7 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService {
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
log.trace("[{}] [{}] device(s) are going to be pushed to edge.", edge.getId(), pageData.getData().size()); log.trace("[{}] [{}] device(s) are going to be pushed to edge.", edge.getId(), pageData.getData().size());
for (Device device : pageData.getData()) { for (Device device : pageData.getData()) {
futures.add(saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.ADDED, device.getId(), null)); saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.ADDED, device.getId(), null);
} }
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink(); pageLink = pageLink.nextPageLink();
@ -368,40 +313,25 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService {
} catch (Exception e) { } catch (Exception e) {
log.error("Exception during loading edge device(s) on sync!", e); log.error("Exception during loading edge device(s) on sync!", e);
} }
return futures;
} }
@Override @Override
public ListenableFuture<Void> processWidgetBundleTypesRequestMsg(TenantId tenantId, Edge edge, public ListenableFuture<Void> processWidgetBundleTypesRequestMsg(TenantId tenantId, Edge edge,
WidgetBundleTypesRequestMsg widgetBundleTypesRequestMsg) { WidgetBundleTypesRequestMsg widgetBundleTypesRequestMsg) {
log.trace("[{}] processWidgetBundleTypesRequestMsg [{}][{}]", tenantId, edge.getName(), widgetBundleTypesRequestMsg); log.trace("[{}] processWidgetBundleTypesRequestMsg [{}][{}]", tenantId, edge.getName(), widgetBundleTypesRequestMsg);
SettableFuture<Void> futureToSet = SettableFuture.create();
if (widgetBundleTypesRequestMsg.getWidgetBundleIdMSB() != 0 && widgetBundleTypesRequestMsg.getWidgetBundleIdLSB() != 0) { if (widgetBundleTypesRequestMsg.getWidgetBundleIdMSB() != 0 && widgetBundleTypesRequestMsg.getWidgetBundleIdLSB() != 0) {
WidgetsBundleId widgetsBundleId = new WidgetsBundleId(new UUID(widgetBundleTypesRequestMsg.getWidgetBundleIdMSB(), widgetBundleTypesRequestMsg.getWidgetBundleIdLSB())); WidgetsBundleId widgetsBundleId = new WidgetsBundleId(new UUID(widgetBundleTypesRequestMsg.getWidgetBundleIdMSB(), widgetBundleTypesRequestMsg.getWidgetBundleIdLSB()));
WidgetsBundle widgetsBundleById = widgetsBundleService.findWidgetsBundleById(tenantId, widgetsBundleId); WidgetsBundle widgetsBundleById = widgetsBundleService.findWidgetsBundleById(tenantId, widgetsBundleId);
List<ListenableFuture<EdgeEvent>> futures = new ArrayList<>();
if (widgetsBundleById != null) { if (widgetsBundleById != null) {
List<WidgetType> widgetTypesToPush = List<WidgetType> widgetTypesToPush =
widgetTypeService.findWidgetTypesByTenantIdAndBundleAlias(widgetsBundleById.getTenantId(), widgetsBundleById.getAlias()); widgetTypeService.findWidgetTypesByTenantIdAndBundleAlias(widgetsBundleById.getTenantId(), widgetsBundleById.getAlias());
for (WidgetType widgetType : widgetTypesToPush) { for (WidgetType widgetType : widgetTypesToPush) {
futures.add(saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.WIDGET_TYPE, EdgeEventActionType.ADDED, widgetType.getId(), null)); saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.WIDGET_TYPE, EdgeEventActionType.ADDED, widgetType.getId(), null);
} }
} }
Futures.addCallback(Futures.allAsList(futures), new FutureCallback<>() {
@Override
public void onSuccess(@Nullable List<EdgeEvent> result) {
futureToSet.set(null);
}
@Override
public void onFailure(Throwable t) {
log.error("Can't sync widget types by widget bundle [{}]", widgetBundleTypesRequestMsg, t);
futureToSet.setException(t);
}
}, dbCallbackExecutorService);
} }
return futureToSet; return Futures.immediateFuture(null);
} }
@Override @Override
@ -416,9 +346,12 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService {
public void onSuccess(@Nullable List<EntityView> entityViews) { public void onSuccess(@Nullable List<EntityView> entityViews) {
try { try {
if (entityViews != null && !entityViews.isEmpty()) { if (entityViews != null && !entityViews.isEmpty()) {
List<ListenableFuture<Boolean>> futures = new ArrayList<>();
for (EntityView entityView : entityViews) { for (EntityView entityView : entityViews) {
Futures.addCallback(relationService.checkRelation(tenantId, edge.getId(), entityView.getId(), ListenableFuture<Boolean> future = relationService.checkRelation(tenantId, edge.getId(), entityView.getId(),
EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE), new FutureCallback<>() { EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE);
futures.add(future);
Futures.addCallback(future, new FutureCallback<>() {
@Override @Override
public void onSuccess(@Nullable Boolean result) { public void onSuccess(@Nullable Boolean result) {
if (Boolean.TRUE.equals(result)) { if (Boolean.TRUE.equals(result)) {
@ -426,16 +359,27 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService {
EdgeEventActionType.ADDED, entityView.getId(), null); EdgeEventActionType.ADDED, entityView.getId(), null);
} }
} }
@Override @Override
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
log.error("Exception during loading relation [{}] to edge on sync!", t, t); // Do nothing - error handles in allAsList
futureToSet.setException(t);
} }
}, dbCallbackExecutorService); }, dbCallbackExecutorService);
} }
Futures.addCallback(Futures.allAsList(futures), new FutureCallback<>() {
@Override
public void onSuccess(@Nullable List<Boolean> result) {
futureToSet.set(null);
}
@Override
public void onFailure(Throwable t) {
log.error("Exception during loading relation [{}] to edge on sync!", t, t);
futureToSet.setException(t);
}
}, dbCallbackExecutorService);
} else {
futureToSet.set(null);
} }
futureToSet.set(null);
} catch (Exception e) { } catch (Exception e) {
log.error("Exception during loading relation(s) to edge on sync!", e); log.error("Exception during loading relation(s) to edge on sync!", e);
futureToSet.setException(e); futureToSet.setException(e);
@ -451,30 +395,19 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService {
return futureToSet; return futureToSet;
} }
private ListenableFuture<EdgeEvent> saveEdgeEvent(TenantId tenantId, private void saveEdgeEvent(TenantId tenantId,
EdgeId edgeId, EdgeId edgeId,
EdgeEventType type, EdgeEventType type,
EdgeEventActionType action, EdgeEventActionType action,
EntityId entityId, EntityId entityId,
JsonNode body) { JsonNode body) {
log.trace("Pushing edge event to edge queue. tenantId [{}], edgeId [{}], type [{}], action[{}], entityId [{}], body [{}]", log.trace("Pushing edge event to edge queue. tenantId [{}], edgeId [{}], type [{}], action[{}], entityId [{}], body [{}]",
tenantId, edgeId, type, action, entityId, body); tenantId, edgeId, type, action, entityId, body);
EdgeEvent edgeEvent = EdgeEventUtils.constructEdgeEvent(tenantId, edgeId, type, action, entityId, body); EdgeEvent edgeEvent = EdgeEventUtils.constructEdgeEvent(tenantId, edgeId, type, action, entityId, body);
ListenableFuture<EdgeEvent> future = edgeEventService.saveAsync(edgeEvent); edgeEventService.save(edgeEvent);
Futures.addCallback(future, new FutureCallback<>() { tbClusterService.onEdgeEventUpdate(tenantId, edgeId);
@Override
public void onSuccess(@Nullable EdgeEvent result) {
tbClusterService.onEdgeEventUpdate(tenantId, edgeId);
}
@Override
public void onFailure(Throwable t) {
log.warn("[{}] Can't save edge event [{}] for edge [{}]", tenantId.getId(), edgeEvent, edgeId.getId(), t);
}
}, dbCallbackExecutorService);
return future;
} }
} }

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

@ -17,12 +17,18 @@ package org.thingsboard.server.service.install;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Profile; import org.springframework.context.annotation.Profile;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
@ -51,6 +57,7 @@ import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.common.data.kv.DoubleDataEntry; import org.thingsboard.server.common.data.kv.DoubleDataEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry;
@ -78,12 +85,19 @@ import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.dao.settings.AdminSettingsService;
import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantProfileService;
import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.dao.widget.WidgetsBundleService; import org.thingsboard.server.dao.widget.WidgetsBundleService;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Service @Service
@Profile("install") @Profile("install")
@ -93,6 +107,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
private static final ObjectMapper objectMapper = new ObjectMapper(); private static final ObjectMapper objectMapper = new ObjectMapper();
public static final String CUSTOMER_CRED = "customer"; public static final String CUSTOMER_CRED = "customer";
public static final String DEFAULT_DEVICE_TYPE = "default"; public static final String DEFAULT_DEVICE_TYPE = "default";
public static final String ACTIVITY_STATE = "active";
@Autowired @Autowired
private InstallScripts installScripts; private InstallScripts installScripts;
@ -133,11 +148,32 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
@Autowired @Autowired
private RuleChainService ruleChainService; private RuleChainService ruleChainService;
@Autowired
private TimeseriesService tsService;
@Value("${state.persistToTelemetry:false}")
@Getter
private boolean persistActivityToTelemetry;
@Bean @Bean
protected BCryptPasswordEncoder passwordEncoder() { protected BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); return new BCryptPasswordEncoder();
} }
private ExecutorService tsCallBackExecutor;
@PostConstruct
public void initExecutor() {
tsCallBackExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("sys-loader-ts-callback"));
}
@PreDestroy
public void shutdownExecutor() {
if (tsCallBackExecutor != null) {
tsCallBackExecutor.shutdownNow();
}
}
@Override @Override
public void createSysAdmin() { public void createSysAdmin() {
createUser(Authority.SYS_ADMIN, null, null, "sysadmin@thingsboard.org", "sysadmin"); createUser(Authority.SYS_ADMIN, null, null, "sysadmin@thingsboard.org", "sysadmin");
@ -215,6 +251,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
node.put("password", ""); node.put("password", "");
node.put("tlsVersion", "TLSv1.2");//NOSONAR, key used to identify password field (not password value itself) node.put("tlsVersion", "TLSv1.2");//NOSONAR, key used to identify password field (not password value itself)
node.put("enableProxy", false); node.put("enableProxy", false);
node.put("showChangePassword", false);
mailSettings.setJsonValue(node); mailSettings.setJsonValue(node);
adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, mailSettings); adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, mailSettings);
} }
@ -480,10 +517,62 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
device.setAdditionalInfo(additionalInfo); device.setAdditionalInfo(additionalInfo);
} }
device = deviceService.saveDevice(device); device = deviceService.saveDevice(device);
save(device.getId(), ACTIVITY_STATE, false);
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(TenantId.SYS_TENANT_ID, device.getId()); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(TenantId.SYS_TENANT_ID, device.getId());
deviceCredentials.setCredentialsId(accessToken); deviceCredentials.setCredentialsId(accessToken);
deviceCredentialsService.updateDeviceCredentials(TenantId.SYS_TENANT_ID, deviceCredentials); deviceCredentialsService.updateDeviceCredentials(TenantId.SYS_TENANT_ID, deviceCredentials);
return device; return device;
} }
private void save(DeviceId deviceId, String key, boolean value) {
if (persistActivityToTelemetry) {
ListenableFuture<Integer> saveFuture = tsService.save(
TenantId.SYS_TENANT_ID,
deviceId,
Collections.singletonList(new BasicTsKvEntry(System.currentTimeMillis(), new BooleanDataEntry(key, value))), 0L);
addTsCallback(saveFuture, new TelemetrySaveCallback<>(deviceId, key, value));
} else {
ListenableFuture<List<Void>> saveFuture = attributesService.save(TenantId.SYS_TENANT_ID, deviceId, DataConstants.SERVER_SCOPE,
Collections.singletonList(new BaseAttributeKvEntry(new BooleanDataEntry(key, value)
, System.currentTimeMillis())));
addTsCallback(saveFuture, new TelemetrySaveCallback<>(deviceId, key, value));
}
}
private static class TelemetrySaveCallback<T> implements FutureCallback<T> {
private final DeviceId deviceId;
private final String key;
private final Object value;
TelemetrySaveCallback(DeviceId deviceId, String key, Object value) {
this.deviceId = deviceId;
this.key = key;
this.value = value;
}
@Override
public void onSuccess(@Nullable T result) {
log.trace("[{}] Successfully updated attribute [{}] with value [{}]", deviceId, key, value);
}
@Override
public void onFailure(Throwable t) {
log.warn("[{}] Failed to update attribute [{}] with value [{}]", deviceId, key, value, t);
}
}
private <S> void addTsCallback(ListenableFuture<S> saveFuture, final FutureCallback<S> callback) {
Futures.addCallback(saveFuture, new FutureCallback<S>() {
@Override
public void onSuccess(@Nullable S result) {
callback.onSuccess(result);
}
@Override
public void onFailure(Throwable t) {
callback.onFailure(t);
}
}, tsCallBackExecutor);
}
} }

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

@ -17,6 +17,8 @@ package org.thingsboard.server.service.ota;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureCallback;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg; import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg;
@ -48,14 +50,16 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateSer
import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.TbQueueProducer;
import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.provider.TbCoreQueueFactory; import org.thingsboard.server.queue.provider.TbCoreQueueFactory;
import org.thingsboard.server.queue.provider.TbRuleEngineQueueFactory;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -64,6 +68,7 @@ import static org.thingsboard.server.common.data.ota.OtaPackageKey.CHECKSUM;
import static org.thingsboard.server.common.data.ota.OtaPackageKey.CHECKSUM_ALGORITHM; import static org.thingsboard.server.common.data.ota.OtaPackageKey.CHECKSUM_ALGORITHM;
import static org.thingsboard.server.common.data.ota.OtaPackageKey.SIZE; import static org.thingsboard.server.common.data.ota.OtaPackageKey.SIZE;
import static org.thingsboard.server.common.data.ota.OtaPackageKey.STATE; import static org.thingsboard.server.common.data.ota.OtaPackageKey.STATE;
import static org.thingsboard.server.common.data.ota.OtaPackageKey.TAG;
import static org.thingsboard.server.common.data.ota.OtaPackageKey.TITLE; import static org.thingsboard.server.common.data.ota.OtaPackageKey.TITLE;
import static org.thingsboard.server.common.data.ota.OtaPackageKey.TS; import static org.thingsboard.server.common.data.ota.OtaPackageKey.TS;
import static org.thingsboard.server.common.data.ota.OtaPackageKey.URL; import static org.thingsboard.server.common.data.ota.OtaPackageKey.URL;
@ -76,7 +81,6 @@ import static org.thingsboard.server.common.data.ota.OtaPackageUtil.getTelemetry
@Slf4j @Slf4j
@Service @Service
@TbCoreComponent
public class DefaultOtaPackageStateService implements OtaPackageStateService { public class DefaultOtaPackageStateService implements OtaPackageStateService {
private final TbClusterService tbClusterService; private final TbClusterService tbClusterService;
@ -86,17 +90,23 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
private final RuleEngineTelemetryService telemetryService; private final RuleEngineTelemetryService telemetryService;
private final TbQueueProducer<TbProtoQueueMsg<ToOtaPackageStateServiceMsg>> otaPackageStateMsgProducer; private final TbQueueProducer<TbProtoQueueMsg<ToOtaPackageStateServiceMsg>> otaPackageStateMsgProducer;
public DefaultOtaPackageStateService(TbClusterService tbClusterService, OtaPackageService otaPackageService, public DefaultOtaPackageStateService(@Lazy TbClusterService tbClusterService,
OtaPackageService otaPackageService,
DeviceService deviceService, DeviceService deviceService,
DeviceProfileService deviceProfileService, DeviceProfileService deviceProfileService,
RuleEngineTelemetryService telemetryService, @Lazy RuleEngineTelemetryService telemetryService,
TbCoreQueueFactory coreQueueFactory) { Optional<TbCoreQueueFactory> coreQueueFactory,
Optional<TbRuleEngineQueueFactory> reQueueFactory) {
this.tbClusterService = tbClusterService; this.tbClusterService = tbClusterService;
this.otaPackageService = otaPackageService; this.otaPackageService = otaPackageService;
this.deviceService = deviceService; this.deviceService = deviceService;
this.deviceProfileService = deviceProfileService; this.deviceProfileService = deviceProfileService;
this.telemetryService = telemetryService; this.telemetryService = telemetryService;
this.otaPackageStateMsgProducer = coreQueueFactory.createToOtaPackageStateServiceMsgProducer(); if (coreQueueFactory.isPresent()) {
this.otaPackageStateMsgProducer = coreQueueFactory.get().createToOtaPackageStateServiceMsgProducer();
} else {
this.otaPackageStateMsgProducer = reQueueFactory.get().createToOtaPackageStateServiceMsgProducer();
}
} }
@Override @Override
@ -246,6 +256,11 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
List<TsKvEntry> telemetry = new ArrayList<>(); List<TsKvEntry> telemetry = new ArrayList<>();
telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(getTargetTelemetryKey(firmware.getType(), TITLE), firmware.getTitle()))); telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(getTargetTelemetryKey(firmware.getType(), TITLE), firmware.getTitle())));
telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(getTargetTelemetryKey(firmware.getType(), VERSION), firmware.getVersion()))); telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(getTargetTelemetryKey(firmware.getType(), VERSION), firmware.getVersion())));
if (StringUtils.isNotEmpty(firmware.getTag())) {
telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(getTargetTelemetryKey(firmware.getType(), TAG), firmware.getTag())));
}
telemetry.add(new BasicTsKvEntry(ts, new LongDataEntry(getTargetTelemetryKey(firmware.getType(), TS), ts))); telemetry.add(new BasicTsKvEntry(ts, new LongDataEntry(getTargetTelemetryKey(firmware.getType(), TS), ts)));
telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(getTelemetryKey(firmware.getType(), STATE), OtaPackageUpdateStatus.QUEUED.name()))); telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(getTelemetryKey(firmware.getType(), STATE), OtaPackageUpdateStatus.QUEUED.name())));
@ -289,6 +304,9 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
List<AttributeKvEntry> attributes = new ArrayList<>(); List<AttributeKvEntry> attributes = new ArrayList<>();
attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, TITLE), otaPackage.getTitle()))); attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, TITLE), otaPackage.getTitle())));
attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, VERSION), otaPackage.getVersion()))); attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, VERSION), otaPackage.getVersion())));
if (StringUtils.isNotEmpty(otaPackage.getTag())) {
attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, TAG), otaPackage.getTag())));
}
if (otaPackage.hasUrl()) { if (otaPackage.hasUrl()) {
attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, URL), otaPackage.getUrl()))); attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, URL), otaPackage.getUrl())));
List<String> attrToRemove = new ArrayList<>(); List<String> attrToRemove = new ArrayList<>();

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

@ -16,11 +16,17 @@
package org.thingsboard.server.service.queue; package org.thingsboard.server.service.queue;
import com.google.protobuf.ByteString; import com.google.protobuf.ByteString;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg; import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.msg.ToDeviceActorNotificationMsg;
import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.ApiUsageState;
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;
@ -29,7 +35,6 @@ import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EdgeId;
@ -56,8 +61,9 @@ import org.thingsboard.server.queue.common.MultipleTbQueueCallbackWrapper;
import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
import org.thingsboard.server.service.ota.OtaPackageStateService;
import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.rpc.FromDeviceRpcResponse; import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
@ -66,10 +72,13 @@ import java.util.concurrent.atomic.AtomicInteger;
@Service @Service
@Slf4j @Slf4j
@RequiredArgsConstructor
public class DefaultTbClusterService implements TbClusterService { public class DefaultTbClusterService implements TbClusterService {
@Value("${cluster.stats.enabled:false}") @Value("${cluster.stats.enabled:false}")
private boolean statsEnabled; private boolean statsEnabled;
@Value("${edges.enabled}")
protected boolean edgesEnabled;
private final AtomicInteger toCoreMsgs = new AtomicInteger(0); private final AtomicInteger toCoreMsgs = new AtomicInteger(0);
private final AtomicInteger toCoreNfs = new AtomicInteger(0); private final AtomicInteger toCoreNfs = new AtomicInteger(0);
@ -81,13 +90,7 @@ public class DefaultTbClusterService implements TbClusterService {
private final PartitionService partitionService; private final PartitionService partitionService;
private final DataDecodingEncodingService encodingService; private final DataDecodingEncodingService encodingService;
private final TbDeviceProfileCache deviceProfileCache; private final TbDeviceProfileCache deviceProfileCache;
private final OtaPackageStateService otaPackageStateService;
public DefaultTbClusterService(TbQueueProducerProvider producerProvider, PartitionService partitionService, DataDecodingEncodingService encodingService, TbDeviceProfileCache deviceProfileCache) {
this.producerProvider = producerProvider;
this.partitionService = partitionService;
this.encodingService = encodingService;
this.deviceProfileCache = deviceProfileCache;
}
@Override @Override
public void pushMsgToCore(TenantId tenantId, EntityId entityId, ToCoreMsg msg, TbQueueCallback callback) { public void pushMsgToCore(TenantId tenantId, EntityId entityId, ToCoreMsg msg, TbQueueCallback callback) {
@ -200,55 +203,52 @@ public class DefaultTbClusterService implements TbClusterService {
} }
@Override @Override
public void onEntityStateChange(TenantId tenantId, EntityId entityId, ComponentLifecycleEvent state) { public void broadcastEntityStateChangeEvent(TenantId tenantId, EntityId entityId, ComponentLifecycleEvent state) {
log.trace("[{}] Processing {} state change event: {}", tenantId, entityId.getEntityType(), state); log.trace("[{}] Processing {} state change event: {}", tenantId, entityId.getEntityType(), state);
broadcast(new ComponentLifecycleMsg(tenantId, entityId, state)); broadcast(new ComponentLifecycleMsg(tenantId, entityId, state));
} }
@Override @Override
public void onDeviceProfileChange(DeviceProfile deviceProfile, TbQueueCallback callback) { public void onDeviceProfileChange(DeviceProfile deviceProfile, TbQueueCallback callback) {
onEntityChange(deviceProfile.getTenantId(), deviceProfile.getId(), deviceProfile, callback); broadcastEntityChangeToTransport(deviceProfile.getTenantId(), deviceProfile.getId(), deviceProfile, callback);
} }
@Override @Override
public void onTenantProfileChange(TenantProfile tenantProfile, TbQueueCallback callback) { public void onTenantProfileChange(TenantProfile tenantProfile, TbQueueCallback callback) {
onEntityChange(TenantId.SYS_TENANT_ID, tenantProfile.getId(), tenantProfile, callback); broadcastEntityChangeToTransport(TenantId.SYS_TENANT_ID, tenantProfile.getId(), tenantProfile, callback);
} }
@Override @Override
public void onTenantChange(Tenant tenant, TbQueueCallback callback) { public void onTenantChange(Tenant tenant, TbQueueCallback callback) {
onEntityChange(TenantId.SYS_TENANT_ID, tenant.getId(), tenant, callback); broadcastEntityChangeToTransport(TenantId.SYS_TENANT_ID, tenant.getId(), tenant, callback);
} }
@Override @Override
public void onApiStateChange(ApiUsageState apiUsageState, TbQueueCallback callback) { public void onApiStateChange(ApiUsageState apiUsageState, TbQueueCallback callback) {
onEntityChange(apiUsageState.getTenantId(), apiUsageState.getId(), apiUsageState, callback); broadcastEntityChangeToTransport(apiUsageState.getTenantId(), apiUsageState.getId(), apiUsageState, callback);
broadcast(new ComponentLifecycleMsg(apiUsageState.getTenantId(), apiUsageState.getId(), ComponentLifecycleEvent.UPDATED)); broadcast(new ComponentLifecycleMsg(apiUsageState.getTenantId(), apiUsageState.getId(), ComponentLifecycleEvent.UPDATED));
} }
@Override @Override
public void onDeviceProfileDelete(DeviceProfile entity, TbQueueCallback callback) { public void onDeviceProfileDelete(DeviceProfile entity, TbQueueCallback callback) {
onEntityDelete(entity.getTenantId(), entity.getId(), entity.getName(), callback); broadcastEntityDeleteToTransport(entity.getTenantId(), entity.getId(), entity.getName(), callback);
} }
@Override @Override
public void onTenantProfileDelete(TenantProfile entity, TbQueueCallback callback) { public void onTenantProfileDelete(TenantProfile entity, TbQueueCallback callback) {
onEntityDelete(TenantId.SYS_TENANT_ID, entity.getId(), entity.getName(), callback); broadcastEntityDeleteToTransport(TenantId.SYS_TENANT_ID, entity.getId(), entity.getName(), callback);
} }
@Override @Override
public void onTenantDelete(Tenant entity, TbQueueCallback callback) { public void onTenantDelete(Tenant entity, TbQueueCallback callback) {
onEntityDelete(TenantId.SYS_TENANT_ID, entity.getId(), entity.getName(), callback); broadcastEntityDeleteToTransport(TenantId.SYS_TENANT_ID, entity.getId(), entity.getName(), callback);
}
@Override
public void onDeviceChange(Device entity, TbQueueCallback callback) {
onEntityChange(entity.getTenantId(), entity.getId(), entity, callback);
} }
@Override @Override
public void onDeviceDeleted(Device entity, TbQueueCallback callback) { public void onDeviceDeleted(Device device, TbQueueCallback callback) {
onEntityDelete(entity.getTenantId(), entity.getId(), entity.getName(), callback); broadcastEntityDeleteToTransport(device.getTenantId(), device.getId(), device.getName(), callback);
sendDeviceStateServiceEvent(device.getTenantId(), device.getId(), false, false, true);
broadcastEntityStateChangeEvent(device.getTenantId(), device.getId(), ComponentLifecycleEvent.DELETED);
} }
@Override @Override
@ -278,7 +278,7 @@ public class DefaultTbClusterService implements TbClusterService {
broadcast(transportMsg, callback); broadcast(transportMsg, callback);
} }
public <T> void onEntityChange(TenantId tenantId, EntityId entityid, T entity, TbQueueCallback callback) { public <T> void broadcastEntityChangeToTransport(TenantId tenantId, EntityId entityid, T entity, TbQueueCallback callback) {
String entityName = (entity instanceof HasName) ? ((HasName) entity).getName() : entity.getClass().getName(); String entityName = (entity instanceof HasName) ? ((HasName) entity).getName() : entity.getClass().getName();
log.trace("[{}][{}][{}] Processing [{}] change event", tenantId, entityid.getEntityType(), entityid.getId(), entityName); log.trace("[{}][{}][{}] Processing [{}] change event", tenantId, entityid.getEntityType(), entityid.getId(), entityName);
TransportProtos.EntityUpdateMsg entityUpdateMsg = TransportProtos.EntityUpdateMsg.newBuilder() TransportProtos.EntityUpdateMsg entityUpdateMsg = TransportProtos.EntityUpdateMsg.newBuilder()
@ -288,7 +288,7 @@ public class DefaultTbClusterService implements TbClusterService {
broadcast(transportMsg, callback); broadcast(transportMsg, callback);
} }
private void onEntityDelete(TenantId tenantId, EntityId entityId, String name, TbQueueCallback callback) { private void broadcastEntityDeleteToTransport(TenantId tenantId, EntityId entityId, String name, TbQueueCallback callback) {
log.trace("[{}][{}][{}] Processing [{}] delete event", tenantId, entityId.getEntityType(), entityId.getId(), name); log.trace("[{}][{}][{}] Processing [{}] delete event", tenantId, entityId.getEntityType(), entityId.getId(), name);
TransportProtos.EntityDeleteMsg entityDeleteMsg = TransportProtos.EntityDeleteMsg.newBuilder() TransportProtos.EntityDeleteMsg entityDeleteMsg = TransportProtos.EntityDeleteMsg.newBuilder()
.setEntityType(entityId.getEntityType().name()) .setEntityType(entityId.getEntityType().name())
@ -369,4 +369,77 @@ public class DefaultTbClusterService implements TbClusterService {
} }
} }
} }
private void sendDeviceStateServiceEvent(TenantId tenantId, DeviceId deviceId, boolean added, boolean updated, boolean deleted) {
TransportProtos.DeviceStateServiceMsgProto.Builder builder = TransportProtos.DeviceStateServiceMsgProto.newBuilder();
builder.setTenantIdMSB(tenantId.getId().getMostSignificantBits());
builder.setTenantIdLSB(tenantId.getId().getLeastSignificantBits());
builder.setDeviceIdMSB(deviceId.getId().getMostSignificantBits());
builder.setDeviceIdLSB(deviceId.getId().getLeastSignificantBits());
builder.setAdded(added);
builder.setUpdated(updated);
builder.setDeleted(deleted);
TransportProtos.DeviceStateServiceMsgProto msg = builder.build();
pushMsgToCore(tenantId, deviceId, TransportProtos.ToCoreMsg.newBuilder().setDeviceStateServiceMsg(msg).build(), null);
}
@Override
public void onDeviceUpdated(Device device, Device old) {
onDeviceUpdated(device, old, true);
}
@Override
public void onDeviceUpdated(Device device, Device old, boolean notifyEdge) {
var created = old == null;
broadcastEntityChangeToTransport(device.getTenantId(), device.getId(), device, null);
if (old != null && (!device.getName().equals(old.getName()) || !device.getType().equals(old.getType()))) {
pushMsgToCore(new DeviceNameOrTypeUpdateMsg(device.getTenantId(), device.getId(), device.getName(), device.getType()), null);
}
broadcastEntityStateChangeEvent(device.getTenantId(), device.getId(), created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
sendDeviceStateServiceEvent(device.getTenantId(), device.getId(), created, !created, false);
otaPackageStateService.update(device, old);
if (!created && notifyEdge) {
sendNotificationMsgToEdgeService(device.getTenantId(), null, device.getId(), null, null, EdgeEventActionType.UPDATED);
}
}
@Override
public void sendNotificationMsgToEdgeService(TenantId tenantId, EdgeId edgeId, EntityId entityId, String body, EdgeEventType type, EdgeEventActionType action) {
if (!edgesEnabled) {
return;
}
if (type == null) {
if (entityId != null) {
type = EdgeUtils.getEdgeEventTypeByEntityType(entityId.getEntityType());
} else {
log.trace("[{}] entity id and type are null. Ignoring this notification", tenantId);
return;
}
if (type == null) {
log.trace("[{}] edge event type is null. Ignoring this notification [{}]", tenantId, entityId);
return;
}
}
TransportProtos.EdgeNotificationMsgProto.Builder builder = TransportProtos.EdgeNotificationMsgProto.newBuilder();
builder.setTenantIdMSB(tenantId.getId().getMostSignificantBits());
builder.setTenantIdLSB(tenantId.getId().getLeastSignificantBits());
builder.setType(type.name());
builder.setAction(action.name());
if (entityId != null) {
builder.setEntityIdMSB(entityId.getId().getMostSignificantBits());
builder.setEntityIdLSB(entityId.getId().getLeastSignificantBits());
builder.setEntityType(entityId.getEntityType().name());
}
if (edgeId != null) {
builder.setEdgeIdMSB(edgeId.getId().getMostSignificantBits());
builder.setEdgeIdLSB(edgeId.getId().getLeastSignificantBits());
}
if (body != null) {
builder.setBody(body);
}
TransportProtos.EdgeNotificationMsgProto msg = builder.build();
log.trace("[{}] sending notification to edge service {}", tenantId.getId(), msg);
pushMsgToCore(tenantId, entityId != null ? entityId : tenantId, TransportProtos.ToCoreMsg.newBuilder().setEdgeNotificationMsg(msg).build(), null);
}
} }

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

@ -26,7 +26,7 @@ import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.rule.engine.api.RpcError; import org.thingsboard.server.common.data.rpc.RpcError;
import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
@ -64,7 +64,7 @@ import org.thingsboard.server.service.ota.OtaPackageStateService;
import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.queue.processing.AbstractConsumerService; import org.thingsboard.server.service.queue.processing.AbstractConsumerService;
import org.thingsboard.server.service.queue.processing.IdMsgPair; import org.thingsboard.server.service.queue.processing.IdMsgPair;
import org.thingsboard.server.service.rpc.FromDeviceRpcResponse; import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse;
import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService; import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService;
import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg; import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg;
import org.thingsboard.server.service.state.DeviceStateService; import org.thingsboard.server.service.state.DeviceStateService;

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

@ -21,7 +21,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.rule.engine.api.RpcError; import org.thingsboard.server.common.data.rpc.RpcError;
import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
@ -55,7 +55,7 @@ import org.thingsboard.server.service.queue.processing.TbRuleEngineProcessingStr
import org.thingsboard.server.service.queue.processing.TbRuleEngineProcessingStrategyFactory; import org.thingsboard.server.service.queue.processing.TbRuleEngineProcessingStrategyFactory;
import org.thingsboard.server.service.queue.processing.TbRuleEngineSubmitStrategy; import org.thingsboard.server.service.queue.processing.TbRuleEngineSubmitStrategy;
import org.thingsboard.server.service.queue.processing.TbRuleEngineSubmitStrategyFactory; import org.thingsboard.server.service.queue.processing.TbRuleEngineSubmitStrategyFactory;
import org.thingsboard.server.service.rpc.FromDeviceRpcResponse; import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse;
import org.thingsboard.server.service.rpc.TbRuleEngineDeviceRpcService; import org.thingsboard.server.service.rpc.TbRuleEngineDeviceRpcService;
import org.thingsboard.server.service.stats.RuleEngineStatisticsService; import org.thingsboard.server.service.stats.RuleEngineStatisticsService;

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

@ -22,18 +22,19 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.rule.engine.api.RpcError; import org.thingsboard.server.common.data.rpc.RpcError;
import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse;
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.SecurityUser;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
@ -138,6 +139,12 @@ public class DefaultTbCoreDeviceRpcService implements TbCoreDeviceRpcService {
} }
} }
@Override
public void processRemoveRpc(RemoveRpcActorMsg removeRpcMsg) {
log.trace("[{}][{}] Processing remove RPC [{}]", removeRpcMsg.getTenantId(), removeRpcMsg.getRequestId(), removeRpcMsg.getDeviceId());
actorContext.tellWithHighPriority(removeRpcMsg);
}
private void sendRpcResponseToTbRuleEngine(String originServiceId, FromDeviceRpcResponse response) { private void sendRpcResponseToTbRuleEngine(String originServiceId, FromDeviceRpcResponse response) {
if (serviceId.equals(originServiceId)) { if (serviceId.equals(originServiceId)) {
if (tbRuleEngineRpcService.isPresent()) { if (tbRuleEngineRpcService.isPresent()) {
@ -159,6 +166,11 @@ public class DefaultTbCoreDeviceRpcService implements TbCoreDeviceRpcService {
metaData.putValue("oneway", Boolean.toString(msg.isOneway())); metaData.putValue("oneway", Boolean.toString(msg.isOneway()));
metaData.putValue(DataConstants.PERSISTENT, Boolean.toString(msg.isPersisted())); metaData.putValue(DataConstants.PERSISTENT, Boolean.toString(msg.isPersisted()));
if (msg.getRetries() != null) {
metaData.putValue(DataConstants.RETRIES, msg.getRetries().toString());
}
Device device = deviceService.findDeviceById(msg.getTenantId(), msg.getDeviceId()); Device device = deviceService.findDeviceById(msg.getTenantId(), msg.getDeviceId());
if (device != null) { if (device != null) {
metaData.putValue("deviceName", device.getName()); metaData.putValue("deviceName", device.getName());
@ -168,6 +180,8 @@ public class DefaultTbCoreDeviceRpcService implements TbCoreDeviceRpcService {
entityNode.put("method", msg.getBody().getMethod()); entityNode.put("method", msg.getBody().getMethod());
entityNode.put("params", msg.getBody().getParams()); entityNode.put("params", msg.getBody().getParams());
entityNode.put(DataConstants.ADDITIONAL_INFO, msg.getAdditionalInfo());
try { try {
TbMsg tbMsg = TbMsg.newMsg(DataConstants.RPC_CALL_FROM_SERVER_TO_DEVICE, msg.getDeviceId(), currentUser.getCustomerId(), metaData, TbMsgDataType.JSON, json.writeValueAsString(entityNode)); TbMsg tbMsg = TbMsg.newMsg(DataConstants.RPC_CALL_FROM_SERVER_TO_DEVICE, msg.getDeviceId(), currentUser.getCustomerId(), metaData, TbMsgDataType.JSON, json.writeValueAsString(entityNode));
clusterService.pushMsgToRuleEngine(msg.getTenantId(), msg.getDeviceId(), tbMsg, null); clusterService.pushMsgToRuleEngine(msg.getTenantId(), msg.getDeviceId(), tbMsg, null);

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

@ -19,18 +19,19 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.rule.engine.api.RpcError; import org.thingsboard.server.common.data.rpc.RpcError;
import org.thingsboard.rule.engine.api.RuleEngineDeviceRpcRequest; import org.thingsboard.rule.engine.api.RuleEngineDeviceRpcRequest;
import org.thingsboard.rule.engine.api.RuleEngineDeviceRpcResponse; import org.thingsboard.rule.engine.api.RuleEngineDeviceRpcResponse;
import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody; import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse;
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.queue.util.TbRuleEngineComponent; import org.thingsboard.server.queue.util.TbRuleEngineComponent;
import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
@ -100,7 +101,7 @@ public class DefaultTbRuleEngineRpcService implements TbRuleEngineDeviceRpcServi
@Override @Override
public void sendRpcRequestToDevice(RuleEngineDeviceRpcRequest src, Consumer<RuleEngineDeviceRpcResponse> consumer) { public void sendRpcRequestToDevice(RuleEngineDeviceRpcRequest src, Consumer<RuleEngineDeviceRpcResponse> consumer) {
ToDeviceRpcRequest request = new ToDeviceRpcRequest(src.getRequestUUID(), src.getTenantId(), src.getDeviceId(), ToDeviceRpcRequest request = new ToDeviceRpcRequest(src.getRequestUUID(), src.getTenantId(), src.getDeviceId(),
src.isOneway(), src.getExpirationTime(), new ToDeviceRpcRequestBody(src.getMethod(), src.getBody()), src.isPersisted()); src.isOneway(), src.getExpirationTime(), new ToDeviceRpcRequestBody(src.getMethod(), src.getBody()), src.isPersisted(), src.getRetries(), src.getAdditionalInfo());
forwardRpcRequestToDeviceActor(request, response -> { forwardRpcRequestToDeviceActor(request, response -> {
if (src.isRestApiCall()) { if (src.isRestApiCall()) {
sendRpcResponseToTbCore(src.getOriginServiceId(), response); sendRpcResponseToTbCore(src.getOriginServiceId(), response);

3
application/src/main/java/org/thingsboard/server/service/rpc/FromDeviceRpcResponseActorMsg.java

@ -18,10 +18,11 @@ package org.thingsboard.server.service.rpc;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.ToString; import lombok.ToString;
import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg; import org.thingsboard.server.common.msg.ToDeviceActorNotificationMsg;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.MsgType; import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse;
@ToString @ToString
@RequiredArgsConstructor @RequiredArgsConstructor

44
application/src/main/java/org/thingsboard/server/service/rpc/RemoveRpcActorMsg.java

@ -0,0 +1,44 @@
/**
* 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.rpc;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.ToDeviceActorNotificationMsg;
import java.util.UUID;
@ToString
@RequiredArgsConstructor
public class RemoveRpcActorMsg implements ToDeviceActorNotificationMsg {
@Getter
private final TenantId tenantId;
@Getter
private final DeviceId deviceId;
@Getter
private final UUID requestId;
@Override
public MsgType getMsgType() {
return MsgType.REMOVE_RPC_TO_DEVICE_ACTOR_MSG;
}
}

3
application/src/main/java/org/thingsboard/server/service/rpc/TbCoreDeviceRpcService.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.service.rpc; package org.thingsboard.server.service.rpc;
import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse;
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.SecurityUser;
@ -55,4 +56,6 @@ public interface TbCoreDeviceRpcService {
*/ */
void processRpcResponseFromDeviceActor(FromDeviceRpcResponse response); void processRpcResponseFromDeviceActor(FromDeviceRpcResponse response);
void processRemoveRpc(RemoveRpcActorMsg removeRpcMsg);
} }

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

@ -31,7 +31,7 @@ import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.rpc.RpcService; import org.thingsboard.server.dao.rpc.RpcService;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
@TbCoreComponent @TbCoreComponent
@Service @Service
@ -63,7 +63,7 @@ public class TbRpcService {
private void pushRpcMsgToRuleEngine(TenantId tenantId, Rpc rpc) { private void pushRpcMsgToRuleEngine(TenantId tenantId, Rpc rpc) {
TbMsg msg = TbMsg.newMsg("RPC_" + rpc.getStatus().name(), rpc.getDeviceId(), TbMsgMetaData.EMPTY, JacksonUtil.toString(rpc)); TbMsg msg = TbMsg.newMsg("RPC_" + rpc.getStatus().name(), rpc.getDeviceId(), TbMsgMetaData.EMPTY, JacksonUtil.toString(rpc));
tbClusterService.pushMsgToRuleEngine(tenantId, rpc.getId(), msg, null); tbClusterService.pushMsgToRuleEngine(tenantId, rpc.getDeviceId(), msg, null);
} }
public Rpc findRpcById(TenantId tenantId, RpcId rpcId) { public Rpc findRpcById(TenantId tenantId, RpcId rpcId) {

1
application/src/main/java/org/thingsboard/server/service/rpc/TbRuleEngineDeviceRpcService.java

@ -16,6 +16,7 @@
package org.thingsboard.server.service.rpc; package org.thingsboard.server.service.rpc;
import org.thingsboard.rule.engine.api.RuleEngineRpcService; import org.thingsboard.rule.engine.api.RuleEngineRpcService;
import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse;
/** /**
* Created by ashvayka on 16.04.18. * Created by ashvayka on 16.04.18.

2
application/src/main/java/org/thingsboard/server/service/rpc/ToDeviceRpcRequestActorMsg.java

@ -18,7 +18,7 @@ package org.thingsboard.server.service.rpc;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.ToString; import lombok.ToString;
import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg; import org.thingsboard.server.common.msg.ToDeviceActorNotificationMsg;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.MsgType; import org.thingsboard.server.common.msg.MsgType;

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

@ -47,7 +47,7 @@ import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.service.install.InstallScripts; import org.thingsboard.server.service.install.InstallScripts;
import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.UserPrincipal; import org.thingsboard.server.service.security.model.UserPrincipal;
@ -180,7 +180,7 @@ public abstract class AbstractOAuth2ClientMapper {
installScripts.createDefaultEdgeRuleChains(tenant.getId()); installScripts.createDefaultEdgeRuleChains(tenant.getId());
tenantProfileCache.evict(tenant.getId()); tenantProfileCache.evict(tenant.getId());
tbClusterService.onTenantChange(tenant, null); tbClusterService.onTenantChange(tenant, null);
tbClusterService.onEntityStateChange(tenant.getId(), tenant.getId(), tbClusterService.broadcastEntityStateChangeEvent(tenant.getId(), tenant.getId(),
ComponentLifecycleEvent.CREATED); ComponentLifecycleEvent.CREATED);
} else { } else {
tenant = tenants.get(0); tenant = tenants.get(0);

11
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java

@ -17,6 +17,7 @@ package org.thingsboard.server.service.security.auth.oauth2;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
@ -29,6 +30,7 @@ import org.thingsboard.server.common.data.oauth2.OAuth2Registration;
import org.thingsboard.server.dao.oauth2.OAuth2User; import org.thingsboard.server.dao.oauth2.OAuth2User;
import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.SecurityUser;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@Service(value = "customOAuth2ClientMapper") @Service(value = "customOAuth2ClientMapper")
@ -40,6 +42,15 @@ public class CustomOAuth2ClientMapper extends AbstractOAuth2ClientMapper impleme
private RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder(); private RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
@PostConstruct
public void init() {
// Register time module to parse Instant objects.
// com.fasterxml.jackson.databind.exc.InvalidDefinitionException:
// Java 8 date/time type `java.time.Instant` not supported by default:
// add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling
json.registerModule(new JavaTimeModule());
}
@Override @Override
public SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) { public SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) {
OAuth2MapperConfig config = registration.getMapperConfig(); OAuth2MapperConfig config = registration.getMapperConfig();

4
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.service.security.auth.oauth2; package org.thingsboard.server.service.security.auth.oauth2;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
@ -42,6 +43,7 @@ import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.UUID; import java.util.UUID;
@Slf4j
@Component(value = "oauth2AuthenticationSuccessHandler") @Component(value = "oauth2AuthenticationSuccessHandler")
public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
@ -99,6 +101,8 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS
clearAuthenticationAttributes(request, response); clearAuthenticationAttributes(request, response);
getRedirectStrategy().sendRedirect(request, response, baseUrl + "/?accessToken=" + accessToken.getToken() + "&refreshToken=" + refreshToken.getToken()); getRedirectStrategy().sendRedirect(request, response, baseUrl + "/?accessToken=" + accessToken.getToken() + "&refreshToken=" + refreshToken.getToken());
} catch (Exception e) { } catch (Exception e) {
log.debug("Error occurred during processing authentication success result. " +
"request [{}], response [{}], authentication [{}]", request, response, authentication, e);
clearAuthenticationAttributes(request, response); clearAuthenticationAttributes(request, response);
String errorPrefix; String errorPrefix;
if (!StringUtils.isEmpty(callbackUrlScheme)) { if (!StringUtils.isEmpty(callbackUrlScheme)) {

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

@ -22,7 +22,6 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningScheduledExecutorService; import com.google.common.util.concurrent.ListeningScheduledExecutorService;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.SettableFuture;
import lombok.Getter; import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -59,7 +58,7 @@ import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.TbApplicationEventListener; import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
@ -71,7 +70,6 @@ import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.Queue; import java.util.Queue;
import java.util.Random; import java.util.Random;
import java.util.Set; import java.util.Set;
@ -137,7 +135,6 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
private ExecutorService deviceStateExecutor; private ExecutorService deviceStateExecutor;
private final ConcurrentMap<TopicPartitionInfo, Set<DeviceId>> partitionedDevices = new ConcurrentHashMap<>(); private final ConcurrentMap<TopicPartitionInfo, Set<DeviceId>> partitionedDevices = new ConcurrentHashMap<>();
final ConcurrentMap<DeviceId, DeviceStateData> deviceStates = new ConcurrentHashMap<>(); final ConcurrentMap<DeviceId, DeviceStateData> deviceStates = new ConcurrentHashMap<>();
private final ConcurrentMap<DeviceId, Long> deviceLastSavedActivity = new ConcurrentHashMap<>();
final Queue<Set<TopicPartitionInfo>> subscribeQueue = new ConcurrentLinkedQueue<>(); final Queue<Set<TopicPartitionInfo>> subscribeQueue = new ConcurrentLinkedQueue<>();
@ -177,22 +174,7 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
} }
@Override @Override
public void onDeviceAdded(Device device) { public void onDeviceConnect(TenantId tenantId, DeviceId deviceId) {
sendDeviceEvent(device.getTenantId(), device.getId(), true, false, false);
}
@Override
public void onDeviceUpdated(Device device) {
sendDeviceEvent(device.getTenantId(), device.getId(), false, true, false);
}
@Override
public void onDeviceDeleted(Device device) {
sendDeviceEvent(device.getTenantId(), device.getId(), false, false, true);
}
@Override
public void onDeviceConnect(DeviceId deviceId) {
log.trace("on Device Connect [{}]", deviceId.getId()); log.trace("on Device Connect [{}]", deviceId.getId());
DeviceStateData stateData = getOrFetchDeviceStateData(deviceId); DeviceStateData stateData = getOrFetchDeviceStateData(deviceId);
long ts = System.currentTimeMillis(); long ts = System.currentTimeMillis();
@ -200,23 +182,23 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
save(deviceId, LAST_CONNECT_TIME, ts); save(deviceId, LAST_CONNECT_TIME, ts);
pushRuleEngineMessage(stateData, CONNECT_EVENT); pushRuleEngineMessage(stateData, CONNECT_EVENT);
checkAndUpdateState(deviceId, stateData); checkAndUpdateState(deviceId, stateData);
cleanDeviceStateIfBelongsExternalPartition(tenantId, deviceId);
} }
@Override @Override
public void onDeviceActivity(DeviceId deviceId, long lastReportedActivity) { public void onDeviceActivity(TenantId tenantId, DeviceId deviceId, long lastReportedActivity) {
log.trace("on Device Activity [{}], lastReportedActivity [{}]", deviceId.getId(), lastReportedActivity); log.trace("on Device Activity [{}], lastReportedActivity [{}]", deviceId.getId(), lastReportedActivity);
long lastSavedActivity = deviceLastSavedActivity.getOrDefault(deviceId, 0L); final DeviceStateData stateData = getOrFetchDeviceStateData(deviceId);
if (lastReportedActivity > 0 && lastReportedActivity > lastSavedActivity) { if (lastReportedActivity > 0 && lastReportedActivity > stateData.getState().getLastActivityTime()) {
final DeviceStateData stateData = getOrFetchDeviceStateData(deviceId);
updateActivityState(deviceId, stateData, lastReportedActivity); updateActivityState(deviceId, stateData, lastReportedActivity);
} }
cleanDeviceStateIfBelongsExternalPartition(tenantId, deviceId);
} }
void updateActivityState(DeviceId deviceId, DeviceStateData stateData, long lastReportedActivity) { void updateActivityState(DeviceId deviceId, DeviceStateData stateData, long lastReportedActivity) {
log.trace("updateActivityState - fetched state {} for device {}, lastReportedActivity {}", stateData, deviceId, lastReportedActivity); log.trace("updateActivityState - fetched state {} for device {}, lastReportedActivity {}", stateData, deviceId, lastReportedActivity);
if (stateData != null) { if (stateData != null) {
save(deviceId, LAST_ACTIVITY_TIME, lastReportedActivity); save(deviceId, LAST_ACTIVITY_TIME, lastReportedActivity);
deviceLastSavedActivity.put(deviceId, lastReportedActivity);
DeviceState state = stateData.getState(); DeviceState state = stateData.getState();
state.setLastActivityTime(lastReportedActivity); state.setLastActivityTime(lastReportedActivity);
if (!state.isActive()) { if (!state.isActive()) {
@ -225,21 +207,23 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
pushRuleEngineMessage(stateData, ACTIVITY_EVENT); pushRuleEngineMessage(stateData, ACTIVITY_EVENT);
} }
} else { } else {
log.warn("updateActivityState - fetched state IN NULL for device {}, lastReportedActivity {}", deviceId, lastReportedActivity); log.debug("updateActivityState - fetched state IN NULL for device {}, lastReportedActivity {}", deviceId, lastReportedActivity);
cleanUpDeviceStateMap(deviceId);
} }
} }
@Override @Override
public void onDeviceDisconnect(DeviceId deviceId) { public void onDeviceDisconnect(TenantId tenantId, DeviceId deviceId) {
DeviceStateData stateData = getOrFetchDeviceStateData(deviceId); DeviceStateData stateData = getOrFetchDeviceStateData(deviceId);
long ts = System.currentTimeMillis(); long ts = System.currentTimeMillis();
stateData.getState().setLastDisconnectTime(ts); stateData.getState().setLastDisconnectTime(ts);
save(deviceId, LAST_DISCONNECT_TIME, ts); save(deviceId, LAST_DISCONNECT_TIME, ts);
pushRuleEngineMessage(stateData, DISCONNECT_EVENT); pushRuleEngineMessage(stateData, DISCONNECT_EVENT);
cleanDeviceStateIfBelongsExternalPartition(tenantId, deviceId);
} }
@Override @Override
public void onDeviceInactivityTimeoutUpdate(DeviceId deviceId, long inactivityTimeout) { public void onDeviceInactivityTimeoutUpdate(TenantId tenantId, DeviceId deviceId, long inactivityTimeout) {
if (inactivityTimeout <= 0L) { if (inactivityTimeout <= 0L) {
return; return;
} }
@ -247,6 +231,7 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
DeviceStateData stateData = getOrFetchDeviceStateData(deviceId); DeviceStateData stateData = getOrFetchDeviceStateData(deviceId);
stateData.getState().setInactivityTimeout(inactivityTimeout); stateData.getState().setInactivityTimeout(inactivityTimeout);
checkAndUpdateState(deviceId, stateData); checkAndUpdateState(deviceId, stateData);
cleanDeviceStateIfBelongsExternalPartition(tenantId, deviceId);
} }
@Override @Override
@ -267,9 +252,10 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, device.getId()); TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, device.getId());
if (partitionedDevices.containsKey(tpi)) { if (partitionedDevices.containsKey(tpi)) {
addDeviceUsingState(tpi, state); addDeviceUsingState(tpi, state);
save(deviceId, ACTIVITY_STATE, false);
callback.onSuccess(); callback.onSuccess();
} else { } else {
log.warn("[{}][{}] Device belongs to external partition. Probably rebalancing is in progress. Topic: {}" log.debug("[{}][{}] Device belongs to external partition. Probably rebalancing is in progress. Topic: {}"
, tenantId, deviceId, tpi.getFullTopicName()); , tenantId, deviceId, tpi.getFullTopicName());
callback.onFailure(new RuntimeException("Device belongs to external partition " + tpi.getFullTopicName() + "!")); callback.onFailure(new RuntimeException("Device belongs to external partition " + tpi.getFullTopicName() + "!"));
} }
@ -283,12 +269,11 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
}, deviceStateExecutor); }, deviceStateExecutor);
} else if (proto.getUpdated()) { } else if (proto.getUpdated()) {
DeviceStateData stateData = getOrFetchDeviceStateData(device.getId()); DeviceStateData stateData = getOrFetchDeviceStateData(device.getId());
if (stateData != null) { TbMsgMetaData md = new TbMsgMetaData();
TbMsgMetaData md = new TbMsgMetaData(); md.putValue("deviceName", device.getName());
md.putValue("deviceName", device.getName()); md.putValue("deviceType", device.getType());
md.putValue("deviceType", device.getType()); stateData.setMetaData(md);
stateData.setMetaData(md); callback.onSuccess();
}
} }
} else { } else {
//Device was probably deleted while message was in queue; //Device was probably deleted while message was in queue;
@ -356,19 +341,18 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
// We no longer manage current partition of devices; // We no longer manage current partition of devices;
removedPartitions.forEach(partition -> { removedPartitions.forEach(partition -> {
Set<DeviceId> devices = partitionedDevices.remove(partition); Set<DeviceId> devices = partitionedDevices.remove(partition);
devices.forEach(deviceId -> { devices.forEach(this::cleanUpDeviceStateMap);
deviceStates.remove(deviceId);
deviceLastSavedActivity.remove(deviceId);
});
}); });
addedPartitions.forEach(tpi -> partitionedDevices.computeIfAbsent(tpi, key -> ConcurrentHashMap.newKeySet())); addedPartitions.forEach(tpi -> partitionedDevices.computeIfAbsent(tpi, key -> ConcurrentHashMap.newKeySet()));
initPartitions(addedPartitions); initPartitions(addedPartitions);
log.info("Managing following partitions:"); scheduledExecutor.submit(() -> {
partitionedDevices.forEach((tpi, devices) -> { log.info("Managing following partitions:");
log.info("[{}]: {} devices", tpi.getFullTopicName(), devices.size()); partitionedDevices.forEach((tpi, devices) -> {
log.info("[{}]: {} devices", tpi.getFullTopicName(), devices.size());
});
}); });
} catch (Throwable t) { } catch (Throwable t) {
log.warn("Failed to init device states from DB", t); log.warn("Failed to init device states from DB", t);
@ -456,18 +440,19 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
deviceIds.add(state.getDeviceId()); deviceIds.add(state.getDeviceId());
deviceStates.put(state.getDeviceId(), state); deviceStates.put(state.getDeviceId(), state);
} else { } else {
log.warn("Device belongs to external partition {}" + tpi.getFullTopicName()); log.debug("[{}] Device belongs to external partition {}", state.getDeviceId(), tpi.getFullTopicName());
new RuntimeException("Device belongs to external partition " + tpi.getFullTopicName() + "!"); throw new RuntimeException("Device belongs to external partition " + tpi.getFullTopicName() + "!");
} }
} }
void updateInactivityStateIfExpired() { void updateInactivityStateIfExpired() {
final long ts = System.currentTimeMillis(); final long ts = System.currentTimeMillis();
log.debug("Calculating state updates for {} devices", deviceStates.size()); partitionedDevices.forEach((tpi, deviceIds) -> {
Set<DeviceId> deviceIds = new HashSet<>(deviceStates.keySet()); log.debug("Calculating state updates. tpi {} for {} devices", tpi.getFullTopicName(), deviceIds.size());
for (DeviceId deviceId : deviceIds) { for (DeviceId deviceId : deviceIds) {
updateInactivityStateIfExpired(ts, deviceId); updateInactivityStateIfExpired(ts, deviceId);
} }
});
} }
void updateInactivityStateIfExpired(long ts, DeviceId deviceId) { void updateInactivityStateIfExpired(long ts, DeviceId deviceId) {
@ -488,8 +473,7 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
} }
} else { } else {
log.debug("[{}] Device that belongs to other server is detected and removed.", deviceId); log.debug("[{}] Device that belongs to other server is detected and removed.", deviceId);
deviceStates.remove(deviceId); cleanUpDeviceStateMap(deviceId);
deviceLastSavedActivity.remove(deviceId);
} }
} }
@ -522,27 +506,26 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
} }
} }
private void sendDeviceEvent(TenantId tenantId, DeviceId deviceId, boolean added, boolean updated, boolean deleted) { private void cleanDeviceStateIfBelongsExternalPartition(TenantId tenantId, final DeviceId deviceId) {
TransportProtos.DeviceStateServiceMsgProto.Builder builder = TransportProtos.DeviceStateServiceMsgProto.newBuilder(); TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, deviceId);
builder.setTenantIdMSB(tenantId.getId().getMostSignificantBits()); if (!partitionedDevices.containsKey(tpi)) {
builder.setTenantIdLSB(tenantId.getId().getLeastSignificantBits()); cleanUpDeviceStateMap(deviceId);
builder.setDeviceIdMSB(deviceId.getId().getMostSignificantBits()); log.debug("[{}][{}] device belongs to external partition. Probably rebalancing is in progress. Topic: {}"
builder.setDeviceIdLSB(deviceId.getId().getLeastSignificantBits()); , tenantId, deviceId, tpi.getFullTopicName());
builder.setAdded(added); }
builder.setUpdated(updated);
builder.setDeleted(deleted);
TransportProtos.DeviceStateServiceMsgProto msg = builder.build();
clusterService.pushMsgToCore(tenantId, deviceId, TransportProtos.ToCoreMsg.newBuilder().setDeviceStateServiceMsg(msg).build(), null);
} }
private void onDeviceDeleted(TenantId tenantId, DeviceId deviceId) { private void onDeviceDeleted(TenantId tenantId, DeviceId deviceId) {
deviceStates.remove(deviceId); cleanUpDeviceStateMap(deviceId);
deviceLastSavedActivity.remove(deviceId);
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, deviceId); TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, deviceId);
Set<DeviceId> deviceIdSet = partitionedDevices.get(tpi); Set<DeviceId> deviceIdSet = partitionedDevices.get(tpi);
deviceIdSet.remove(deviceId); deviceIdSet.remove(deviceId);
} }
private void cleanUpDeviceStateMap(DeviceId deviceId) {
deviceStates.remove(deviceId);
}
private ListenableFuture<DeviceStateData> fetchDeviceState(Device device) { private ListenableFuture<DeviceStateData> fetchDeviceState(Device device) {
ListenableFuture<DeviceStateData> future; ListenableFuture<DeviceStateData> future;
if (persistToTelemetry) { if (persistToTelemetry) {
@ -573,7 +556,7 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
} }
private <T extends KvEntry> Function<List<T>, DeviceStateData> extractDeviceStateData(Device device) { private <T extends KvEntry> Function<List<T>, DeviceStateData> extractDeviceStateData(Device device) {
return new Function<List<T>, DeviceStateData>() { return new Function<>() {
@Nonnull @Nonnull
@Override @Override
public DeviceStateData apply(@Nullable List<T> data) { public DeviceStateData apply(@Nullable List<T> data) {
@ -660,9 +643,9 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
tsSubService.saveAndNotifyInternal( tsSubService.saveAndNotifyInternal(
TenantId.SYS_TENANT_ID, deviceId, TenantId.SYS_TENANT_ID, deviceId,
Collections.singletonList(new BasicTsKvEntry(System.currentTimeMillis(), new LongDataEntry(key, value))), Collections.singletonList(new BasicTsKvEntry(System.currentTimeMillis(), new LongDataEntry(key, value))),
new AttributeSaveCallback<>(deviceId, key, value)); new TelemetrySaveCallback<>(deviceId, key, value));
} else { } else {
tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, DataConstants.SERVER_SCOPE, key, value, new AttributeSaveCallback<>(deviceId, key, value)); tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, DataConstants.SERVER_SCOPE, key, value, new TelemetrySaveCallback<>(deviceId, key, value));
} }
} }
@ -671,18 +654,18 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
tsSubService.saveAndNotifyInternal( tsSubService.saveAndNotifyInternal(
TenantId.SYS_TENANT_ID, deviceId, TenantId.SYS_TENANT_ID, deviceId,
Collections.singletonList(new BasicTsKvEntry(System.currentTimeMillis(), new BooleanDataEntry(key, value))), Collections.singletonList(new BasicTsKvEntry(System.currentTimeMillis(), new BooleanDataEntry(key, value))),
new AttributeSaveCallback<>(deviceId, key, value)); new TelemetrySaveCallback<>(deviceId, key, value));
} else { } else {
tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, DataConstants.SERVER_SCOPE, key, value, new AttributeSaveCallback<>(deviceId, key, value)); tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, DataConstants.SERVER_SCOPE, key, value, new TelemetrySaveCallback<>(deviceId, key, value));
} }
} }
private static class AttributeSaveCallback<T> implements FutureCallback<T> { private static class TelemetrySaveCallback<T> implements FutureCallback<T> {
private final DeviceId deviceId; private final DeviceId deviceId;
private final String key; private final String key;
private final Object value; private final Object value;
AttributeSaveCallback(DeviceId deviceId, String key, Object value) { TelemetrySaveCallback(DeviceId deviceId, String key, Object value) {
this.deviceId = deviceId; this.deviceId = deviceId;
this.key = key; this.key = key;
this.value = value; this.value = value;

15
application/src/main/java/org/thingsboard/server/service/state/DeviceStateService.java

@ -18,6 +18,7 @@ package org.thingsboard.server.service.state;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TbCallback;
@ -27,19 +28,13 @@ import org.thingsboard.server.common.msg.queue.TbCallback;
*/ */
public interface DeviceStateService extends ApplicationListener<PartitionChangeEvent> { public interface DeviceStateService extends ApplicationListener<PartitionChangeEvent> {
void onDeviceAdded(Device device); void onDeviceConnect(TenantId tenantId, DeviceId deviceId);
void onDeviceUpdated(Device device); void onDeviceActivity(TenantId tenantId, DeviceId deviceId, long lastReportedActivityTime);
void onDeviceDeleted(Device device); void onDeviceDisconnect(TenantId tenantId, DeviceId deviceId);
void onDeviceConnect(DeviceId deviceId); void onDeviceInactivityTimeoutUpdate(TenantId tenantId, DeviceId deviceId, long inactivityTimeout);
void onDeviceActivity(DeviceId deviceId, long lastReportedActivityTime);
void onDeviceDisconnect(DeviceId deviceId);
void onDeviceInactivityTimeoutUpdate(DeviceId deviceId, long inactivityTimeout);
void onQueueMsg(TransportProtos.DeviceStateServiceMsgProto proto, TbCallback bytes); void onQueueMsg(TransportProtos.DeviceStateServiceMsgProto proto, TbCallback bytes);

20
application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java

@ -31,6 +31,7 @@ import org.thingsboard.server.common.data.kv.Aggregation;
import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.common.data.kv.ReadTsKvQuery; import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry;
@ -52,7 +53,7 @@ import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.service.state.DefaultDeviceStateService; import org.thingsboard.server.service.state.DefaultDeviceStateService;
import org.thingsboard.server.service.state.DeviceStateService; import org.thingsboard.server.service.state.DeviceStateService;
import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate; import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate;
@ -222,6 +223,9 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
} }
return subscriptionUpdate; return subscriptionUpdate;
}); });
if (entityId.getEntityType() == EntityType.DEVICE) {
updateDeviceInactivityTimeout(tenantId, entityId, ts);
}
callback.onSuccess(); callback.onSuccess();
} }
@ -255,11 +259,7 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
}); });
if (entityId.getEntityType() == EntityType.DEVICE) { if (entityId.getEntityType() == EntityType.DEVICE) {
if (TbAttributeSubscriptionScope.SERVER_SCOPE.name().equalsIgnoreCase(scope)) { if (TbAttributeSubscriptionScope.SERVER_SCOPE.name().equalsIgnoreCase(scope)) {
for (AttributeKvEntry attribute : attributes) { updateDeviceInactivityTimeout(tenantId, entityId, attributes);
if (attribute.getKey().equals(DefaultDeviceStateService.INACTIVITY_TIMEOUT)) {
deviceStateService.onDeviceInactivityTimeoutUpdate(new DeviceId(entityId.getId()), attribute.getLongValue().orElse(0L));
}
}
} else if (TbAttributeSubscriptionScope.SHARED_SCOPE.name().equalsIgnoreCase(scope) && notifyDevice) { } else if (TbAttributeSubscriptionScope.SHARED_SCOPE.name().equalsIgnoreCase(scope) && notifyDevice) {
clusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onUpdate(tenantId, clusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onUpdate(tenantId,
new DeviceId(entityId.getId()), DataConstants.SHARED_SCOPE, new ArrayList<>(attributes)) new DeviceId(entityId.getId()), DataConstants.SHARED_SCOPE, new ArrayList<>(attributes))
@ -269,6 +269,14 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
callback.onSuccess(); callback.onSuccess();
} }
private void updateDeviceInactivityTimeout(TenantId tenantId, EntityId entityId, List<? extends KvEntry> kvEntries) {
for (KvEntry kvEntry : kvEntries) {
if (kvEntry.getKey().equals(DefaultDeviceStateService.INACTIVITY_TIMEOUT)) {
deviceStateService.onDeviceInactivityTimeoutUpdate(tenantId, new DeviceId(entityId.getId()), kvEntry.getLongValue().orElse(0L));
}
}
}
@Override @Override
public void onAlarmUpdate(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback) { public void onAlarmUpdate(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback) {
onLocalAlarmSubUpdate(entityId, onLocalAlarmSubUpdate(entityId,

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

@ -30,7 +30,7 @@ import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.queue.discovery.TbApplicationEventListener; import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate; import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate;
import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate; import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate;
@ -42,7 +42,6 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Slf4j @Slf4j
@TbCoreComponent @TbCoreComponent

4
application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java

@ -20,15 +20,13 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.EventListener;
import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.TbApplicationEventListener; import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.service.subscription.SubscriptionManagerService; import org.thingsboard.server.service.subscription.SubscriptionManagerService;
import javax.annotation.Nullable; import javax.annotation.Nullable;

2
application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java

@ -46,7 +46,7 @@ import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.usagestats.TbApiUsageClient; import org.thingsboard.server.queue.usagestats.TbApiUsageClient;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.service.subscription.SubscriptionManagerService; import org.thingsboard.server.service.subscription.SubscriptionManagerService;
import org.thingsboard.server.service.subscription.TbSubscriptionUtils; import org.thingsboard.server.service.subscription.TbSubscriptionUtils;

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

@ -45,7 +45,7 @@ import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.usagestats.TbApiUsageClient; import org.thingsboard.server.queue.usagestats.TbApiUsageClient;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.service.subscription.TbSubscriptionUtils; import org.thingsboard.server.service.subscription.TbSubscriptionUtils;
import javax.annotation.Nullable; import javax.annotation.Nullable;

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

@ -41,8 +41,10 @@ import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.TenantProfile;
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.ProvisionDeviceCredentialsData; import org.thingsboard.server.common.data.device.credentials.ProvisionDeviceCredentialsData;
import org.thingsboard.server.common.data.device.data.CoapDeviceTransportConfiguration;
import org.thingsboard.server.common.data.device.data.Lwm2mDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.Lwm2mDeviceTransportConfiguration;
import org.thingsboard.server.common.data.device.data.PowerMode; import org.thingsboard.server.common.data.device.data.PowerMode;
import org.thingsboard.server.common.data.device.data.PowerSavingConfiguration;
import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileCredentials; import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileCredentials;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
@ -93,7 +95,7 @@ import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.service.resource.TbResourceService; import org.thingsboard.server.service.resource.TbResourceService;
import org.thingsboard.server.service.state.DeviceStateService; import org.thingsboard.server.service.state.DeviceStateService;
@ -263,9 +265,11 @@ public class DefaultTransportApiService implements TransportApiService {
device.setCustomerId(gateway.getCustomerId()); device.setCustomerId(gateway.getCustomerId());
DeviceProfile deviceProfile = deviceProfileCache.findOrCreateDeviceProfile(gateway.getTenantId(), requestMsg.getDeviceType()); DeviceProfile deviceProfile = deviceProfileCache.findOrCreateDeviceProfile(gateway.getTenantId(), requestMsg.getDeviceType());
device.setDeviceProfileId(deviceProfile.getId()); device.setDeviceProfileId(deviceProfile.getId());
device = deviceService.saveDevice(device); Device savedDevice = deviceService.saveDevice(device);
tbClusterService.onDeviceUpdated(savedDevice, null);
device = savedDevice;
relationService.saveRelationAsync(TenantId.SYS_TENANT_ID, new EntityRelation(gateway.getId(), device.getId(), "Created")); relationService.saveRelationAsync(TenantId.SYS_TENANT_ID, new EntityRelation(gateway.getId(), device.getId(), "Created"));
deviceStateService.onDeviceAdded(device);
TbMsgMetaData metaData = new TbMsgMetaData(); TbMsgMetaData metaData = new TbMsgMetaData();
CustomerId customerId = gateway.getCustomerId(); CustomerId customerId = gateway.getCustomerId();
@ -461,16 +465,6 @@ public class DefaultTransportApiService implements TransportApiService {
} }
private DeviceInfoProto getDeviceInfoProto(Device device) throws JsonProcessingException { private DeviceInfoProto getDeviceInfoProto(Device device) throws JsonProcessingException {
PowerMode powerMode = null;
Long edrxCycle = null;
switch (device.getDeviceData().getTransportConfiguration().getType()) {
case LWM2M:
Lwm2mDeviceTransportConfiguration transportConfiguration = (Lwm2mDeviceTransportConfiguration) device.getDeviceData().getTransportConfiguration();
powerMode = transportConfiguration.getPowerMode();
edrxCycle = transportConfiguration.getEdrxCycle();
break;
}
DeviceInfoProto.Builder builder = DeviceInfoProto.newBuilder() DeviceInfoProto.Builder builder = DeviceInfoProto.newBuilder()
.setTenantIdMSB(device.getTenantId().getId().getMostSignificantBits()) .setTenantIdMSB(device.getTenantId().getId().getMostSignificantBits())
.setTenantIdLSB(device.getTenantId().getId().getLeastSignificantBits()) .setTenantIdLSB(device.getTenantId().getId().getLeastSignificantBits())
@ -483,9 +477,28 @@ public class DefaultTransportApiService implements TransportApiService {
.setDeviceProfileIdMSB(device.getDeviceProfileId().getId().getMostSignificantBits()) .setDeviceProfileIdMSB(device.getDeviceProfileId().getId().getMostSignificantBits())
.setDeviceProfileIdLSB(device.getDeviceProfileId().getId().getLeastSignificantBits()) .setDeviceProfileIdLSB(device.getDeviceProfileId().getId().getLeastSignificantBits())
.setAdditionalInfo(mapper.writeValueAsString(device.getAdditionalInfo())); .setAdditionalInfo(mapper.writeValueAsString(device.getAdditionalInfo()));
if (powerMode != null) {
builder.setPowerMode(powerMode.name()); PowerSavingConfiguration psmConfiguration = null;
builder.setEdrxCycle(edrxCycle); switch (device.getDeviceData().getTransportConfiguration().getType()) {
case LWM2M:
psmConfiguration = (Lwm2mDeviceTransportConfiguration) device.getDeviceData().getTransportConfiguration();
break;
case COAP:
psmConfiguration = (CoapDeviceTransportConfiguration) device.getDeviceData().getTransportConfiguration();
break;
}
if (psmConfiguration != null) {
PowerMode powerMode = psmConfiguration.getPowerMode();
if (powerMode != null) {
builder.setPowerMode(powerMode.name());
if (powerMode.equals(PowerMode.PSM)) {
builder.setPsmActivityTimer(checkLong(psmConfiguration.getPsmActivityTimer()));
} else if (powerMode.equals(PowerMode.E_DRX)) {
builder.setEdrxCycle(checkLong(psmConfiguration.getEdrxCycle()));
builder.setPagingTransmissionWindow(checkLong(psmConfiguration.getPagingTransmissionWindow()));
}
}
} }
return builder.build(); return builder.build();
} }
@ -570,7 +583,7 @@ public class DefaultTransportApiService implements TransportApiService {
device.setName(deviceName); device.setName(deviceName);
device.setType("LwM2M"); device.setType("LwM2M");
device = deviceService.saveDevice(device); device = deviceService.saveDevice(device);
deviceStateService.onDeviceAdded(device); tbClusterService.onDeviceUpdated(device, null);
} }
TransportProtos.LwM2MRegistrationResponseMsg registrationResponseMsg = TransportProtos.LwM2MRegistrationResponseMsg registrationResponseMsg =
TransportProtos.LwM2MRegistrationResponseMsg.newBuilder() TransportProtos.LwM2MRegistrationResponseMsg.newBuilder()
@ -584,4 +597,8 @@ public class DefaultTransportApiService implements TransportApiService {
deviceCreationLock.unlock(); deviceCreationLock.unlock();
} }
} }
private Long checkLong(Long l) {
return l != null ? l : 0;
}
} }

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

@ -228,57 +228,57 @@ cassandra:
# SQL configuration parameters # SQL configuration parameters
sql: sql:
# Specify batch size for persisting attribute updates # Specify batch size for persisting attribute updates
attributes: attributes:
batch_size: "${SQL_ATTRIBUTES_BATCH_SIZE:10000}" batch_size: "${SQL_ATTRIBUTES_BATCH_SIZE:10000}"
batch_max_delay: "${SQL_ATTRIBUTES_BATCH_MAX_DELAY_MS:100}" batch_max_delay: "${SQL_ATTRIBUTES_BATCH_MAX_DELAY_MS:100}"
stats_print_interval_ms: "${SQL_ATTRIBUTES_BATCH_STATS_PRINT_MS:10000}" stats_print_interval_ms: "${SQL_ATTRIBUTES_BATCH_STATS_PRINT_MS:10000}"
batch_threads: "${SQL_ATTRIBUTES_BATCH_THREADS:4}" batch_threads: "${SQL_ATTRIBUTES_BATCH_THREADS:4}"
ts: ts:
batch_size: "${SQL_TS_BATCH_SIZE:10000}" batch_size: "${SQL_TS_BATCH_SIZE:10000}"
batch_max_delay: "${SQL_TS_BATCH_MAX_DELAY_MS:100}" batch_max_delay: "${SQL_TS_BATCH_MAX_DELAY_MS:100}"
stats_print_interval_ms: "${SQL_TS_BATCH_STATS_PRINT_MS:10000}" stats_print_interval_ms: "${SQL_TS_BATCH_STATS_PRINT_MS:10000}"
batch_threads: "${SQL_TS_BATCH_THREADS:4}" batch_threads: "${SQL_TS_BATCH_THREADS:4}"
ts_latest: ts_latest:
batch_size: "${SQL_TS_LATEST_BATCH_SIZE:10000}" batch_size: "${SQL_TS_LATEST_BATCH_SIZE:10000}"
batch_max_delay: "${SQL_TS_LATEST_BATCH_MAX_DELAY_MS:100}" batch_max_delay: "${SQL_TS_LATEST_BATCH_MAX_DELAY_MS:100}"
stats_print_interval_ms: "${SQL_TS_LATEST_BATCH_STATS_PRINT_MS:10000}" stats_print_interval_ms: "${SQL_TS_LATEST_BATCH_STATS_PRINT_MS:10000}"
batch_threads: "${SQL_TS_LATEST_BATCH_THREADS:4}" batch_threads: "${SQL_TS_LATEST_BATCH_THREADS:4}"
update_by_latest_ts: "${SQL_TS_UPDATE_BY_LATEST_TIMESTAMP:true}" update_by_latest_ts: "${SQL_TS_UPDATE_BY_LATEST_TIMESTAMP:true}"
# Specify whether to sort entities before batch update. Should be enabled for cluster mode to avoid deadlocks # Specify whether to sort entities before batch update. Should be enabled for cluster mode to avoid deadlocks
batch_sort: "${SQL_BATCH_SORT:false}" batch_sort: "${SQL_BATCH_SORT:false}"
# Specify whether to remove null characters from strValue of attributes and timeseries before insert # Specify whether to remove null characters from strValue of attributes and timeseries before insert
remove_null_chars: "${SQL_REMOVE_NULL_CHARS:true}" remove_null_chars: "${SQL_REMOVE_NULL_CHARS:true}"
# Specify whether to log database queries and their parameters generated by entity query repository # Specify whether to log database queries and their parameters generated by entity query repository
log_queries: "${SQL_LOG_QUERIES:false}" log_queries: "${SQL_LOG_QUERIES:false}"
log_queries_threshold: "${SQL_LOG_QUERIES_THRESHOLD:5000}" log_queries_threshold: "${SQL_LOG_QUERIES_THRESHOLD:5000}"
postgres: postgres:
# Specify partitioning size for timestamp key-value storage. Example: DAYS, MONTHS, YEARS, INDEFINITE. # Specify partitioning size for timestamp key-value storage. Example: DAYS, MONTHS, YEARS, INDEFINITE.
ts_key_value_partitioning: "${SQL_POSTGRES_TS_KV_PARTITIONING:MONTHS}" ts_key_value_partitioning: "${SQL_POSTGRES_TS_KV_PARTITIONING:MONTHS}"
timescale: timescale:
# Specify Interval size for new data chunks storage. # Specify Interval size for new data chunks storage.
chunk_time_interval: "${SQL_TIMESCALE_CHUNK_TIME_INTERVAL:604800000}" chunk_time_interval: "${SQL_TIMESCALE_CHUNK_TIME_INTERVAL:604800000}"
batch_threads: "${SQL_TIMESCALE_BATCH_THREADS:4}" batch_threads: "${SQL_TIMESCALE_BATCH_THREADS:4}"
ttl: ttl:
ts: ts:
enabled: "${SQL_TTL_TS_ENABLED:true}" enabled: "${SQL_TTL_TS_ENABLED:true}"
execution_interval_ms: "${SQL_TTL_TS_EXECUTION_INTERVAL:86400000}" # Number of milliseconds. The current value corresponds to one day execution_interval_ms: "${SQL_TTL_TS_EXECUTION_INTERVAL:86400000}" # Number of milliseconds. The current value corresponds to one day
ts_key_value_ttl: "${SQL_TTL_TS_TS_KEY_VALUE_TTL:0}" # Number of seconds ts_key_value_ttl: "${SQL_TTL_TS_TS_KEY_VALUE_TTL:0}" # Number of seconds
events: events:
enabled: "${SQL_TTL_EVENTS_ENABLED:true}" enabled: "${SQL_TTL_EVENTS_ENABLED:true}"
execution_interval_ms: "${SQL_TTL_EVENTS_EXECUTION_INTERVAL:2220000}" # Number of milliseconds (max random initial delay and fixed period). # 37minutes to avoid common interval spikes execution_interval_ms: "${SQL_TTL_EVENTS_EXECUTION_INTERVAL:2220000}" # Number of milliseconds (max random initial delay and fixed period). # 37minutes to avoid common interval spikes
events_ttl: "${SQL_TTL_EVENTS_EVENTS_TTL:0}" # Number of seconds events_ttl: "${SQL_TTL_EVENTS_EVENTS_TTL:0}" # Number of seconds
debug_events_ttl: "${SQL_TTL_EVENTS_DEBUG_EVENTS_TTL:604800}" # Number of seconds. The current value corresponds to one week debug_events_ttl: "${SQL_TTL_EVENTS_DEBUG_EVENTS_TTL:604800}" # Number of seconds. The current value corresponds to one week
edge_events: edge_events:
enabled: "${SQL_TTL_EDGE_EVENTS_ENABLED:true}" enabled: "${SQL_TTL_EDGE_EVENTS_ENABLED:true}"
execution_interval_ms: "${SQL_TTL_EDGE_EVENTS_EXECUTION_INTERVAL:86400000}" # Number of milliseconds. The current value corresponds to one day execution_interval_ms: "${SQL_TTL_EDGE_EVENTS_EXECUTION_INTERVAL:86400000}" # Number of milliseconds. The current value corresponds to one day
edge_events_ttl: "${SQL_TTL_EDGE_EVENTS_TTL:2628000}" # Number of seconds. The current value corresponds to one month edge_events_ttl: "${SQL_TTL_EDGE_EVENTS_TTL:2628000}" # Number of seconds. The current value corresponds to one month
alarms: alarms:
checking_interval: "${SQL_ALARMS_TTL_CHECKING_INTERVAL:7200000}" # Number of milliseconds. The current value corresponds to two hours checking_interval: "${SQL_ALARMS_TTL_CHECKING_INTERVAL:7200000}" # Number of milliseconds. The current value corresponds to two hours
removal_batch_size: "${SQL_ALARMS_TTL_REMOVAL_BATCH_SIZE:3000}" # To delete outdated alarms not all at once but in batches removal_batch_size: "${SQL_ALARMS_TTL_REMOVAL_BATCH_SIZE:3000}" # To delete outdated alarms not all at once but in batches
rpc: rpc:
enabled: "${SQL_TTL_RPC_ENABLED:true}" enabled: "${SQL_TTL_RPC_ENABLED:true}"
checking_interval: "${SQL_RPC_TTL_CHECKING_INTERVAL:7200000}" # Number of milliseconds. The current value corresponds to two hours checking_interval: "${SQL_RPC_TTL_CHECKING_INTERVAL:7200000}" # Number of milliseconds. The current value corresponds to two hours
# Actor system parameters # Actor system parameters
actors: actors:
@ -326,6 +326,9 @@ actors:
queue_size: "${ACTORS_RULE_TRANSACTION_QUEUE_SIZE:15000}" queue_size: "${ACTORS_RULE_TRANSACTION_QUEUE_SIZE:15000}"
# Time in milliseconds for transaction to complete # Time in milliseconds for transaction to complete
duration: "${ACTORS_RULE_TRANSACTION_DURATION:60000}" duration: "${ACTORS_RULE_TRANSACTION_DURATION:60000}"
rpc:
max_retries: "${ACTORS_RPC_MAX_RETRIES:5}"
sequential: "${ACTORS_RPC_SEQUENTIAL:false}"
statistics: statistics:
# Enable/disable actor statistics # Enable/disable actor statistics
enabled: "${ACTORS_STATISTICS_ENABLED:true}" enabled: "${ACTORS_STATISTICS_ENABLED:true}"
@ -344,34 +347,34 @@ caffeine:
specs: specs:
relations: relations:
timeToLiveInMinutes: 1440 timeToLiveInMinutes: 1440
maxSize: 0 maxSize: 10000 # maxSize: 0 means the cache is disabled
deviceCredentials: deviceCredentials:
timeToLiveInMinutes: 1440 timeToLiveInMinutes: 1440
maxSize: 0 maxSize: 10000
devices: devices:
timeToLiveInMinutes: 1440 timeToLiveInMinutes: 1440
maxSize: 0 maxSize: 10000
sessions: sessions:
timeToLiveInMinutes: 1440 timeToLiveInMinutes: 1440
maxSize: 0 maxSize: 10000
assets: assets:
timeToLiveInMinutes: 1440 timeToLiveInMinutes: 1440
maxSize: 0 maxSize: 10000
entityViews: entityViews:
timeToLiveInMinutes: 1440 timeToLiveInMinutes: 1440
maxSize: 0 maxSize: 10000
claimDevices: claimDevices:
timeToLiveInMinutes: 1440 timeToLiveInMinutes: 1440
maxSize: 1000 maxSize: 1000
securitySettings: securitySettings:
timeToLiveInMinutes: 1440 timeToLiveInMinutes: 1440
maxSize: 0 maxSize: 10000
tenantProfiles: tenantProfiles:
timeToLiveInMinutes: 1440 timeToLiveInMinutes: 1440
maxSize: 0 maxSize: 10000
deviceProfiles: deviceProfiles:
timeToLiveInMinutes: 1440 timeToLiveInMinutes: 1440
maxSize: 0 maxSize: 10000
attributes: attributes:
timeToLiveInMinutes: 1440 timeToLiveInMinutes: 1440
maxSize: 100000 maxSize: 100000
@ -386,7 +389,7 @@ caffeine:
maxSize: 10 maxSize: 10
edges: edges:
timeToLiveInMinutes: 1440 timeToLiveInMinutes: 1440
maxSize: 0 maxSize: 10000
redis: redis:
# standalone or cluster # standalone or cluster
@ -452,6 +455,9 @@ spring.mvc.cors:
#Set whether credentials are supported. When not set, credentials are not supported. #Set whether credentials are supported. When not set, credentials are not supported.
allow-credentials: "true" allow-credentials: "true"
# The default timeout for asynchronous requests in milliseconds
spring.mvc.async.request-timeout: "${SPRING_MVC_ASYNC_REQUEST_TIMEOUT:30000}"
# spring serve gzip compressed static resources # spring serve gzip compressed static resources
spring.resources.chain: spring.resources.chain:
compressed: "true" compressed: "true"
@ -563,7 +569,7 @@ js:
transport: transport:
sessions: sessions:
inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}" inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}"
report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:30000}" report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:3000}"
json: json:
# Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON # Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON
type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}"
@ -573,6 +579,9 @@ transport:
timeout: "${CLIENT_SIDE_RPC_TIMEOUT:60000}" timeout: "${CLIENT_SIDE_RPC_TIMEOUT:60000}"
# Enable/disable http/mqtt/coap transport protocols (has higher priority than certain protocol's 'enabled' property) # Enable/disable http/mqtt/coap transport protocols (has higher priority than certain protocol's 'enabled' property)
api_enabled: "${TB_TRANSPORT_API_ENABLED:true}" api_enabled: "${TB_TRANSPORT_API_ENABLED:true}"
log:
enabled: "${TB_TRANSPORT_LOG_ENABLED:true}"
max_length: "${TB_TRANSPORT_LOG_MAX_LENGTH:1024}"
# Local HTTP transport parameters # Local HTTP transport parameters
http: http:
enabled: "${HTTP_ENABLED:true}" enabled: "${HTTP_ENABLED:true}"
@ -585,6 +594,7 @@ transport:
bind_address: "${MQTT_BIND_ADDRESS:0.0.0.0}" bind_address: "${MQTT_BIND_ADDRESS:0.0.0.0}"
bind_port: "${MQTT_BIND_PORT:1883}" bind_port: "${MQTT_BIND_PORT:1883}"
timeout: "${MQTT_TIMEOUT:10000}" timeout: "${MQTT_TIMEOUT:10000}"
msg_queue_size_per_device_limit: "${MQTT_MSG_QUEUE_SIZE_PER_DEVICE_LIMIT:100}" # messages await in the queue before device connected state. This limit works on low level before TenantProfileLimits mechanism
netty: netty:
leak_detector_level: "${NETTY_LEAK_DETECTOR_LVL:DISABLED}" leak_detector_level: "${NETTY_LEAK_DETECTOR_LVL:DISABLED}"
boss_group_thread_count: "${NETTY_BOSS_GROUP_THREADS:1}" boss_group_thread_count: "${NETTY_BOSS_GROUP_THREADS:1}"
@ -618,6 +628,8 @@ transport:
bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}" bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}"
bind_port: "${COAP_BIND_PORT:5683}" bind_port: "${COAP_BIND_PORT:5683}"
timeout: "${COAP_TIMEOUT:10000}" timeout: "${COAP_TIMEOUT:10000}"
psm_activity_timer: "${COAP_PSM_ACTIVITY_TIMER:10000}"
paging_transmission_window: "${COAP_PAGING_TRANSMISSION_WINDOW:10000}"
dtls: dtls:
# Enable/disable DTLS 1.2 support # Enable/disable DTLS 1.2 support
enabled: "${COAP_DTLS_ENABLED:false}" enabled: "${COAP_DTLS_ENABLED:false}"
@ -693,7 +705,7 @@ transport:
# Edges parameters # Edges parameters
edges: edges:
enabled: "${EDGES_ENABLED:false}" enabled: "${EDGES_ENABLED:true}"
rpc: rpc:
port: "${EDGES_RPC_PORT:7070}" port: "${EDGES_RPC_PORT:7070}"
client_max_keep_alive_time_sec: "${EDGES_RPC_CLIENT_MAX_KEEP_ALIVE_TIME_SEC:300}" client_max_keep_alive_time_sec: "${EDGES_RPC_CLIENT_MAX_KEEP_ALIVE_TIME_SEC:300}"
@ -742,6 +754,8 @@ queue:
compression.type: "${TB_KAFKA_COMPRESSION_TYPE:none}" # none or gzip compression.type: "${TB_KAFKA_COMPRESSION_TYPE:none}" # none or gzip
batch.size: "${TB_KAFKA_BATCH_SIZE:16384}" batch.size: "${TB_KAFKA_BATCH_SIZE:16384}"
linger.ms: "${TB_KAFKA_LINGER_MS:1}" linger.ms: "${TB_KAFKA_LINGER_MS:1}"
max.request.size: "${TB_KAFKA_MAX_REQUEST_SIZE:1048576}"
max.in.flight.requests.per.connection: "${TB_KAFKA_MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION:5}"
buffer.memory: "${TB_BUFFER_MEMORY:33554432}" buffer.memory: "${TB_BUFFER_MEMORY:33554432}"
replication_factor: "${TB_QUEUE_KAFKA_REPLICATION_FACTOR:1}" replication_factor: "${TB_QUEUE_KAFKA_REPLICATION_FACTOR:1}"
max_poll_interval_ms: "${TB_QUEUE_KAFKA_MAX_POLL_INTERVAL_MS:300000}" max_poll_interval_ms: "${TB_QUEUE_KAFKA_MAX_POLL_INTERVAL_MS:300000}"
@ -758,7 +772,11 @@ queue:
tb_ota_package: tb_ota_package:
- key: max.poll.records - key: max.poll.records
value: 10 value: 10
other: 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
value: "${TB_QUEUE_KAFKA_REQUEST_TIMEOUT_MS:30000}" # (30 seconds)
- key: "session.timeout.ms" # refer to https://docs.confluent.io/platform/current/installation/configuration/consumer-configs.html#consumerconfigs_session.timeout.ms
value: "${TB_QUEUE_KAFKA_SESSION_TIMEOUT_MS:10000}" # (10 seconds)
topic-properties: topic-properties:
rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}"
core: "${TB_QUEUE_KAFKA_CORE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" core: "${TB_QUEUE_KAFKA_CORE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}"

55
application/src/test/java/org/thingsboard/server/cache/CaffeineCacheDefaultConfigurationTestSuite.java

@ -0,0 +1,55 @@
/**
* 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.cache;
import lombok.extern.slf4j.Slf4j;
import org.assertj.core.api.SoftAssertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.context.SpringBootContextLoader;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = CaffeineCacheDefaultConfigurationTestSuite.class, loader = SpringBootContextLoader.class)
@ComponentScan({"org.thingsboard.server.cache"})
@EnableConfigurationProperties
@Slf4j
public class CaffeineCacheDefaultConfigurationTestSuite {
@Autowired
CaffeineCacheConfiguration caffeineCacheConfiguration;
@Test
public void verifyTransactionAwareCacheManagerProxy() {
assertThat(caffeineCacheConfiguration.getSpecs()).as("specs").isNotNull();
caffeineCacheConfiguration.getSpecs().forEach((name, cacheSpecs)->assertThat(cacheSpecs).as("cache %s specs", name).isNotNull());
SoftAssertions softly = new SoftAssertions();
caffeineCacheConfiguration.getSpecs().forEach((name, cacheSpecs)->{
softly.assertThat(name).as("cache name").isNotEmpty();
softly.assertThat(cacheSpecs.getTimeToLiveInMinutes()).as("cache %s time to live", name).isGreaterThan(0);
softly.assertThat(cacheSpecs.getMaxSize()).as("cache %s max size", name).isGreaterThan(0);
});
softly.assertAll();
}
}

2
application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java

@ -35,7 +35,7 @@ import java.util.function.Predicate;
/** /**
* Created by ashvayka on 20.03.18. * Created by ashvayka on 20.03.18.
*/ */
public class AbstractRuleEngineControllerTest extends AbstractControllerTest { public abstract class AbstractRuleEngineControllerTest extends AbstractControllerTest {
@Autowired @Autowired
protected RuleChainService ruleChainService; protected RuleChainService ruleChainService;

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

@ -31,6 +31,7 @@ import org.junit.Test;
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.DeviceProfileInfo; import org.thingsboard.server.common.data.DeviceProfileInfo;
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.Tenant; import org.thingsboard.server.common.data.Tenant;
@ -102,6 +103,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController
Assert.assertEquals(deviceProfile.getProfileData(), savedDeviceProfile.getProfileData()); Assert.assertEquals(deviceProfile.getProfileData(), savedDeviceProfile.getProfileData());
Assert.assertEquals(deviceProfile.isDefault(), savedDeviceProfile.isDefault()); Assert.assertEquals(deviceProfile.isDefault(), savedDeviceProfile.isDefault());
Assert.assertEquals(deviceProfile.getDefaultRuleChainId(), savedDeviceProfile.getDefaultRuleChainId()); Assert.assertEquals(deviceProfile.getDefaultRuleChainId(), savedDeviceProfile.getDefaultRuleChainId());
Assert.assertEquals(DeviceProfileProvisionType.DISABLED, savedDeviceProfile.getProvisionType());
savedDeviceProfile.setName("New device profile"); savedDeviceProfile.setName("New device profile");
doPost("/api/deviceProfile", savedDeviceProfile, DeviceProfile.class); doPost("/api/deviceProfile", savedDeviceProfile, DeviceProfile.class);
DeviceProfile foundDeviceProfile = doGet("/api/deviceProfile/"+savedDeviceProfile.getId().getId().toString(), DeviceProfile.class); DeviceProfile foundDeviceProfile = doGet("/api/deviceProfile/"+savedDeviceProfile.getId().getId().toString(), DeviceProfile.class);

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

@ -37,6 +37,7 @@ import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.edge.imitator.EdgeImitator; import org.thingsboard.server.edge.imitator.EdgeImitator;
import org.thingsboard.server.gen.edge.v1.AdminSettingsUpdateMsg;
import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg; import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg; import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg; import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg;
@ -685,26 +686,26 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
EdgeImitator edgeImitator = new EdgeImitator("localhost", 7070, edge.getRoutingKey(), edge.getSecret()); EdgeImitator edgeImitator = new EdgeImitator("localhost", 7070, edge.getRoutingKey(), edge.getSecret());
edgeImitator.ignoreType(UserCredentialsUpdateMsg.class); edgeImitator.ignoreType(UserCredentialsUpdateMsg.class);
edgeImitator.expectMessageAmount(7); edgeImitator.expectMessageAmount(11);
edgeImitator.connect(); edgeImitator.connect();
Assert.assertTrue(edgeImitator.waitForMessages()); Assert.assertTrue(edgeImitator.waitForMessages());
Assert.assertEquals(7, edgeImitator.getDownlinkMsgs().size());
Assert.assertEquals(2, edgeImitator.findAllMessagesByType(RuleChainUpdateMsg.class).size()); // one msg during sync process, another from edge creation Assert.assertEquals(2, edgeImitator.findAllMessagesByType(RuleChainUpdateMsg.class).size()); // one msg during sync process, another from edge creation
Assert.assertEquals(1, edgeImitator.findAllMessagesByType(DeviceProfileUpdateMsg.class).size()); // one msg during sync process for 'default' device profile Assert.assertEquals(1, edgeImitator.findAllMessagesByType(DeviceProfileUpdateMsg.class).size()); // one msg during sync process for 'default' device profile
Assert.assertEquals(1, edgeImitator.findAllMessagesByType(DeviceUpdateMsg.class).size()); // one msg once device assigned to edge Assert.assertEquals(1, edgeImitator.findAllMessagesByType(DeviceUpdateMsg.class).size()); // one msg once device assigned to edge
Assert.assertEquals(2, edgeImitator.findAllMessagesByType(AssetUpdateMsg.class).size()); // two msgs - one during sync process, and one more once asset assigned to edge Assert.assertEquals(2, edgeImitator.findAllMessagesByType(AssetUpdateMsg.class).size()); // two msgs - one during sync process, and one more once asset assigned to edge
Assert.assertEquals(1, edgeImitator.findAllMessagesByType(UserUpdateMsg.class).size()); // one msg during sync process for tenant admin user Assert.assertEquals(1, edgeImitator.findAllMessagesByType(UserUpdateMsg.class).size()); // one msg during sync process for tenant admin user
Assert.assertEquals(4, edgeImitator.findAllMessagesByType(AdminSettingsUpdateMsg.class).size());
edgeImitator.expectMessageAmount(4); edgeImitator.expectMessageAmount(8);
doPost("/api/edge/sync/" + edge.getId()); doPost("/api/edge/sync/" + edge.getId());
Assert.assertTrue(edgeImitator.waitForMessages()); Assert.assertTrue(edgeImitator.waitForMessages());
Assert.assertEquals(4, edgeImitator.getDownlinkMsgs().size());
Assert.assertEquals(1, edgeImitator.findAllMessagesByType(RuleChainUpdateMsg.class).size()); Assert.assertEquals(1, edgeImitator.findAllMessagesByType(RuleChainUpdateMsg.class).size());
Assert.assertEquals(1, edgeImitator.findAllMessagesByType(DeviceProfileUpdateMsg.class).size()); Assert.assertEquals(1, edgeImitator.findAllMessagesByType(DeviceProfileUpdateMsg.class).size());
Assert.assertEquals(1, edgeImitator.findAllMessagesByType(AssetUpdateMsg.class).size()); Assert.assertEquals(1, edgeImitator.findAllMessagesByType(AssetUpdateMsg.class).size());
Assert.assertEquals(1, edgeImitator.findAllMessagesByType(UserUpdateMsg.class).size()); Assert.assertEquals(1, edgeImitator.findAllMessagesByType(UserUpdateMsg.class).size());
Assert.assertEquals(4, edgeImitator.findAllMessagesByType(AdminSettingsUpdateMsg.class).size());
edgeImitator.allowIgnoredTypes(); edgeImitator.allowIgnoredTypes();
try { try {

35
application/src/test/java/org/thingsboard/server/controller/BaseEdgeEventControllerTest.java

@ -17,6 +17,7 @@ package org.thingsboard.server.controller;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.awaitility.Awaitility;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
@ -28,18 +29,19 @@ import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.Authority;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Slf4j @Slf4j
public class BaseEdgeEventControllerTest extends AbstractControllerTest { public abstract class BaseEdgeEventControllerTest extends AbstractControllerTest {
private Tenant savedTenant; private Tenant savedTenant;
private User tenantAdmin; private User tenantAdmin;
@ -83,34 +85,37 @@ public class BaseEdgeEventControllerTest extends AbstractControllerTest {
Device device = constructDevice("TestDevice", "default"); Device device = constructDevice("TestDevice", "default");
Device savedDevice = doPost("/api/device", device, Device.class); Device savedDevice = doPost("/api/device", device, Device.class);
doPost("/api/edge/" + edge.getId().toString() + "/device/" + savedDevice.getId().toString(), Device.class); final EdgeId edgeId = edge.getId();
doPost("/api/edge/" + edgeId.toString() + "/device/" + savedDevice.getId().toString(), Device.class);
Asset asset = constructAsset("TestAsset", "default"); Asset asset = constructAsset("TestAsset", "default");
Asset savedAsset = doPost("/api/asset", asset, Asset.class); Asset savedAsset = doPost("/api/asset", asset, Asset.class);
doPost("/api/edge/" + edge.getId().toString() + "/asset/" + savedAsset.getId().toString(), Asset.class); doPost("/api/edge/" + edgeId.toString() + "/asset/" + savedAsset.getId().toString(), Asset.class);
EntityRelation relation = new EntityRelation(savedAsset.getId(), savedDevice.getId(), EntityRelation.CONTAINS_TYPE); EntityRelation relation = new EntityRelation(savedAsset.getId(), savedDevice.getId(), EntityRelation.CONTAINS_TYPE);
doPost("/api/relation", relation); doPost("/api/relation", relation);
// wait while edge event for the relation entity persisted to DB Awaitility.await()
Thread.sleep(100); .atMost(30, TimeUnit.SECONDS)
List<EdgeEvent> edgeEvents; .until(() -> {
int attempt = 1; List<EdgeEvent> edgeEvents = findEdgeEvents(edgeId);
do { return edgeEvents.size() == 4;
edgeEvents = doGetTypedWithTimePageLink("/api/edge/" + edge.getId().toString() + "/events?", });
new TypeReference<PageData<EdgeEvent>>() {}, new TimePageLink(4)).getData(); List<EdgeEvent> edgeEvents = findEdgeEvents(edgeId);
attempt++;
Thread.sleep(100);
} while (edgeEvents.size() != 4 && attempt < 5);
Assert.assertEquals(4, edgeEvents.size());
Assert.assertTrue(edgeEvents.stream().anyMatch(ee -> EdgeEventType.RULE_CHAIN.equals(ee.getType()))); Assert.assertTrue(edgeEvents.stream().anyMatch(ee -> EdgeEventType.RULE_CHAIN.equals(ee.getType())));
Assert.assertTrue(edgeEvents.stream().anyMatch(ee -> EdgeEventType.DEVICE.equals(ee.getType()))); Assert.assertTrue(edgeEvents.stream().anyMatch(ee -> EdgeEventType.DEVICE.equals(ee.getType())));
Assert.assertTrue(edgeEvents.stream().anyMatch(ee -> EdgeEventType.ASSET.equals(ee.getType()))); Assert.assertTrue(edgeEvents.stream().anyMatch(ee -> EdgeEventType.ASSET.equals(ee.getType())));
Assert.assertTrue(edgeEvents.stream().anyMatch(ee -> EdgeEventType.RELATION.equals(ee.getType()))); Assert.assertTrue(edgeEvents.stream().anyMatch(ee -> EdgeEventType.RELATION.equals(ee.getType())));
} }
private List<EdgeEvent> findEdgeEvents(EdgeId edgeId) throws Exception {
return doGetTypedWithTimePageLink("/api/edge/" + edgeId.toString() + "/events?",
new TypeReference<PageData<EdgeEvent>>() {
}, new TimePageLink(10)).getData();
}
private Device constructDevice(String name, String type) { private Device constructDevice(String name, String type) {
Device device = new Device(); Device device = new Device();
device.setName(name); device.setName(name);

2
application/src/test/java/org/thingsboard/server/controller/BaseWebsocketApiTest.java

@ -68,7 +68,7 @@ import java.util.concurrent.TimeUnit;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Slf4j @Slf4j
public class BaseWebsocketApiTest extends AbstractWebsocketTest { public abstract class BaseWebsocketApiTest extends AbstractWebsocketTest {
private Tenant savedTenant; private Tenant savedTenant;
private User tenantAdmin; private User tenantAdmin;

109
application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java

@ -27,6 +27,7 @@ import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.MessageLite; import com.google.protobuf.MessageLite;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
import org.awaitility.Awaitility;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
@ -85,6 +86,7 @@ import org.thingsboard.server.common.transport.adaptor.JsonConverter;
import org.thingsboard.server.controller.AbstractControllerTest; import org.thingsboard.server.controller.AbstractControllerTest;
import org.thingsboard.server.dao.edge.EdgeEventService; import org.thingsboard.server.dao.edge.EdgeEventService;
import org.thingsboard.server.edge.imitator.EdgeImitator; import org.thingsboard.server.edge.imitator.EdgeImitator;
import org.thingsboard.server.gen.edge.v1.AdminSettingsUpdateMsg;
import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg; import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg;
import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg; import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg;
import org.thingsboard.server.gen.edge.v1.AttributeDeleteMsg; import org.thingsboard.server.gen.edge.v1.AttributeDeleteMsg;
@ -114,7 +116,7 @@ import org.thingsboard.server.gen.edge.v1.UserUpdateMsg;
import org.thingsboard.server.gen.edge.v1.WidgetTypeUpdateMsg; import org.thingsboard.server.gen.edge.v1.WidgetTypeUpdateMsg;
import org.thingsboard.server.gen.edge.v1.WidgetsBundleUpdateMsg; import org.thingsboard.server.gen.edge.v1.WidgetsBundleUpdateMsg;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -171,7 +173,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
installation(); installation();
edgeImitator = new EdgeImitator("localhost", 7070, edge.getRoutingKey(), edge.getSecret()); edgeImitator = new EdgeImitator("localhost", 7070, edge.getRoutingKey(), edge.getSecret());
edgeImitator.expectMessageAmount(9); edgeImitator.expectMessageAmount(13);
edgeImitator.connect(); edgeImitator.connect();
testReceivedInitialData(); testReceivedInitialData();
@ -327,9 +329,44 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
testAutoGeneratedCodeByProtobuf(ruleChainUpdateMsg); testAutoGeneratedCodeByProtobuf(ruleChainUpdateMsg);
validateAdminSettings();
log.info("Received data checked"); log.info("Received data checked");
} }
private void validateAdminSettings() throws JsonProcessingException {
List<AdminSettingsUpdateMsg> adminSettingsUpdateMsgs = edgeImitator.findAllMessagesByType(AdminSettingsUpdateMsg.class);
Assert.assertEquals(4, adminSettingsUpdateMsgs.size());
for (AdminSettingsUpdateMsg adminSettingsUpdateMsg : adminSettingsUpdateMsgs) {
if (adminSettingsUpdateMsg.getKey().equals("mail")) {
validateMailAdminSettings(adminSettingsUpdateMsg);
}
if (adminSettingsUpdateMsg.getKey().equals("mailTemplates")) {
validateMailTemplatesAdminSettings(adminSettingsUpdateMsg);
}
}
}
private void validateMailAdminSettings(AdminSettingsUpdateMsg adminSettingsUpdateMsg) throws JsonProcessingException {
JsonNode jsonNode = mapper.readTree(adminSettingsUpdateMsg.getJsonValue());
Assert.assertNotNull(jsonNode.get("mailFrom"));
Assert.assertNotNull(jsonNode.get("smtpProtocol"));
Assert.assertNotNull(jsonNode.get("smtpHost"));
Assert.assertNotNull(jsonNode.get("smtpPort"));
Assert.assertNotNull(jsonNode.get("timeout"));
}
private void validateMailTemplatesAdminSettings(AdminSettingsUpdateMsg adminSettingsUpdateMsg) throws JsonProcessingException {
JsonNode jsonNode = mapper.readTree(adminSettingsUpdateMsg.getJsonValue());
Assert.assertNotNull(jsonNode.get("accountActivated"));
Assert.assertNotNull(jsonNode.get("accountLockout"));
Assert.assertNotNull(jsonNode.get("activation"));
Assert.assertNotNull(jsonNode.get("passwordWasReset"));
Assert.assertNotNull(jsonNode.get("resetPassword"));
Assert.assertNotNull(jsonNode.get("test"));
}
private void testDevices() throws Exception { private void testDevices() throws Exception {
log.info("Testing devices"); log.info("Testing devices");
@ -730,7 +767,15 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
doDelete("/api/alarm/" + savedAlarm.getId().getId().toString()) doDelete("/api/alarm/" + savedAlarm.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
Assert.assertFalse(edgeImitator.waitForMessages(1)); Assert.assertTrue(edgeImitator.waitForMessages(1));
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof AlarmUpdateMsg);
alarmUpdateMsg = (AlarmUpdateMsg) latestMessage;
Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, alarmUpdateMsg.getMsgType());
Assert.assertEquals(alarmUpdateMsg.getType(), savedAlarm.getType());
Assert.assertEquals(alarmUpdateMsg.getName(), savedAlarm.getName());
Assert.assertEquals(alarmUpdateMsg.getOriginatorName(), device.getName());
Assert.assertEquals(alarmUpdateMsg.getStatus(), AlarmStatus.CLEARED_ACK.name());
log.info("Alarms tested successfully"); log.info("Alarms tested successfully");
} }
@ -939,7 +984,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
String timeseriesData = "{\"data\":{\"temperature\":25},\"ts\":" + System.currentTimeMillis() + "}"; String timeseriesData = "{\"data\":{\"temperature\":25},\"ts\":" + System.currentTimeMillis() + "}";
JsonNode timeseriesEntityData = mapper.readTree(timeseriesData); JsonNode timeseriesEntityData = mapper.readTree(timeseriesData);
EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.TIMESERIES_UPDATED, device.getId().getId(), EdgeEventType.DEVICE, timeseriesEntityData); EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.TIMESERIES_UPDATED, device.getId().getId(), EdgeEventType.DEVICE, timeseriesEntityData);
edgeEventService.saveAsync(edgeEvent); edgeEventService.save(edgeEvent);
clusterService.onEdgeEventUpdate(tenantId, edge.getId()); clusterService.onEdgeEventUpdate(tenantId, edge.getId());
Assert.assertTrue(edgeImitator.waitForMessages()); Assert.assertTrue(edgeImitator.waitForMessages());
@ -978,7 +1023,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
JsonNode attributesEntityData = mapper.readTree(attributesData); JsonNode attributesEntityData = mapper.readTree(attributesData);
EdgeEvent edgeEvent1 = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.ATTRIBUTES_UPDATED, device.getId().getId(), EdgeEventType.DEVICE, attributesEntityData); EdgeEvent edgeEvent1 = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.ATTRIBUTES_UPDATED, device.getId().getId(), EdgeEventType.DEVICE, attributesEntityData);
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
edgeEventService.saveAsync(edgeEvent1); edgeEventService.save(edgeEvent1);
clusterService.onEdgeEventUpdate(tenantId, edge.getId()); clusterService.onEdgeEventUpdate(tenantId, edge.getId());
Assert.assertTrue(edgeImitator.waitForMessages()); Assert.assertTrue(edgeImitator.waitForMessages());
@ -1003,7 +1048,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
JsonNode postAttributesEntityData = mapper.readTree(postAttributesData); JsonNode postAttributesEntityData = mapper.readTree(postAttributesData);
EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.POST_ATTRIBUTES, device.getId().getId(), EdgeEventType.DEVICE, postAttributesEntityData); EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.POST_ATTRIBUTES, device.getId().getId(), EdgeEventType.DEVICE, postAttributesEntityData);
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
edgeEventService.saveAsync(edgeEvent); edgeEventService.save(edgeEvent);
clusterService.onEdgeEventUpdate(tenantId, edge.getId()); clusterService.onEdgeEventUpdate(tenantId, edge.getId());
Assert.assertTrue(edgeImitator.waitForMessages()); Assert.assertTrue(edgeImitator.waitForMessages());
@ -1028,7 +1073,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
JsonNode deleteAttributesEntityData = mapper.readTree(deleteAttributesData); JsonNode deleteAttributesEntityData = mapper.readTree(deleteAttributesData);
EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.ATTRIBUTES_DELETED, device.getId().getId(), EdgeEventType.DEVICE, deleteAttributesEntityData); EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.ATTRIBUTES_DELETED, device.getId().getId(), EdgeEventType.DEVICE, deleteAttributesEntityData);
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
edgeEventService.saveAsync(edgeEvent); edgeEventService.save(edgeEvent);
clusterService.onEdgeEventUpdate(tenantId, edge.getId()); clusterService.onEdgeEventUpdate(tenantId, edge.getId());
Assert.assertTrue(edgeImitator.waitForMessages()); Assert.assertTrue(edgeImitator.waitForMessages());
@ -1062,7 +1107,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.RPC_CALL, device.getId().getId(), EdgeEventType.DEVICE, body); EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.RPC_CALL, device.getId().getId(), EdgeEventType.DEVICE, body);
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
edgeEventService.saveAsync(edgeEvent); edgeEventService.save(edgeEvent);
clusterService.onEdgeEventUpdate(tenantId, edge.getId()); clusterService.onEdgeEventUpdate(tenantId, edge.getId());
Assert.assertTrue(edgeImitator.waitForMessages()); Assert.assertTrue(edgeImitator.waitForMessages());
@ -1088,7 +1133,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
JsonNode timeseriesEntityData = mapper.readTree(timeseriesData); JsonNode timeseriesEntityData = mapper.readTree(timeseriesData);
EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.TIMESERIES_UPDATED, EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.TIMESERIES_UPDATED,
device.getId().getId(), EdgeEventType.DEVICE, timeseriesEntityData); device.getId().getId(), EdgeEventType.DEVICE, timeseriesEntityData);
edgeEventService.saveAsync(edgeEvent); edgeEventService.save(edgeEvent);
clusterService.onEdgeEventUpdate(tenantId, edge.getId()); clusterService.onEdgeEventUpdate(tenantId, edge.getId());
} }
@ -1348,24 +1393,24 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.sendUplinkMsg(uplinkMsgBuilder2.build()); edgeImitator.sendUplinkMsg(uplinkMsgBuilder2.build());
Assert.assertTrue(edgeImitator.waitForResponses()); Assert.assertTrue(edgeImitator.waitForResponses());
int attempt = 0; Awaitility.await()
Map<String, List<Map<String, String>>> timeseries; .atMost(2, TimeUnit.SECONDS)
do { .until(() -> loadDeviceTimeseries(device, timeseriesKey).containsKey(timeseriesKey));
timeseries = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + device.getUuidId() + "/values/timeseries?keys=" + timeseriesKey,
new TypeReference<>() {}); Map<String, List<Map<String, String>>> timeseries = loadDeviceTimeseries(device, timeseriesKey);
// Wait before device attributes saved to database before requesting them from controller
Thread.sleep(100);
attempt++;
} while (!timeseries.containsKey(timeseriesKey) || attempt < 10);
Assert.assertTrue(timeseries.containsKey(timeseriesKey)); Assert.assertTrue(timeseries.containsKey(timeseriesKey));
Assert.assertEquals(1, timeseries.get(timeseriesKey).size()); Assert.assertEquals(1, timeseries.get(timeseriesKey).size());
Assert.assertEquals(timeseriesValue, timeseries.get(timeseriesKey).get(0).get("value")); Assert.assertEquals(timeseriesValue, timeseries.get(timeseriesKey).get(0).get("value"));
List<Map<String, String>> attributes = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + device.getId() + "/values/attributes/" + DataConstants.SERVER_SCOPE, new TypeReference<>() {}); List<Map<String, String>> attributes = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + device.getId() + "/values/attributes/" + DataConstants.SERVER_SCOPE, new TypeReference<>() {});
Assert.assertEquals(1, attributes.size()); Assert.assertEquals(2, attributes.size());
Assert.assertEquals(attributes.get(0).get("key"), attributesKey); var result = attributes.stream().filter(kv -> kv.get("key").equals(attributesKey)).filter(kv -> kv.get("value").equals(attributesValue)).findFirst();
Assert.assertEquals(attributes.get(0).get("value"), attributesValue); Assert.assertTrue(result.isPresent());
}
private Map<String, List<Map<String, String>>> loadDeviceTimeseries(Device device, String timeseriesKey) throws Exception {
return doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + device.getUuidId() + "/values/timeseries?keys=" + timeseriesKey,
new TypeReference<>() {});
} }
private void sendRelation() throws Exception { private void sendRelation() throws Exception {
@ -1447,7 +1492,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());
Assert.assertTrue(edgeImitator.waitForResponses()); Assert.assertTrue(edgeImitator.waitForResponses());
Assert.assertTrue(edgeImitator.waitForMessages());; Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage(); AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof RuleChainMetadataUpdateMsg); Assert.assertTrue(latestMessage instanceof RuleChainMetadataUpdateMsg);
@ -1558,11 +1603,14 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
private void sendAttributesRequest() throws Exception { private void sendAttributesRequest() throws Exception {
Device device = findDeviceByName("Edge Device 1"); Device device = findDeviceByName("Edge Device 1");
sendAttributesRequest(device, DataConstants.SERVER_SCOPE, "{\"key1\":\"value1\"}", "key1", "value1"); sendAttributesRequest(device, DataConstants.SERVER_SCOPE, "{\"key1\":\"value1\"}",
sendAttributesRequest(device, DataConstants.SHARED_SCOPE, "{\"key2\":\"value2\"}", "key2", "value2"); "key1", "value1", 2);
sendAttributesRequest(device, DataConstants.SHARED_SCOPE, "{\"key2\":\"value2\"}",
"key2", "value2", 1);
} }
private void sendAttributesRequest(Device device, String scope, String attributesDataStr, String expectedKey, String expectedValue) throws Exception { private void sendAttributesRequest(Device device, String scope, String attributesDataStr, String expectedKey,
String expectedValue, int expectedSize) throws Exception {
JsonNode attributesData = mapper.readTree(attributesDataStr); JsonNode attributesData = mapper.readTree(attributesDataStr);
doPost("/api/plugins/telemetry/DEVICE/" + device.getId().getId().toString() + "/attributes/" + scope, doPost("/api/plugins/telemetry/DEVICE/" + device.getId().getId().toString() + "/attributes/" + scope,
@ -1598,10 +1646,13 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertTrue(latestEntityDataMsg.hasAttributesUpdatedMsg()); Assert.assertTrue(latestEntityDataMsg.hasAttributesUpdatedMsg());
TransportProtos.PostAttributeMsg attributesUpdatedMsg = latestEntityDataMsg.getAttributesUpdatedMsg(); TransportProtos.PostAttributeMsg attributesUpdatedMsg = latestEntityDataMsg.getAttributesUpdatedMsg();
Assert.assertEquals(1, attributesUpdatedMsg.getKvCount()); Assert.assertEquals(expectedSize, attributesUpdatedMsg.getKvList().size());
TransportProtos.KeyValueProto keyValueProto = attributesUpdatedMsg.getKv(0); for (TransportProtos.KeyValueProto keyValueProto : attributesUpdatedMsg.getKvList()) {
Assert.assertEquals(expectedKey, keyValueProto.getKey()); if (keyValueProto.getKey().equals(expectedKey)) {
Assert.assertEquals(expectedValue, keyValueProto.getStringV()); Assert.assertEquals(expectedKey, keyValueProto.getKey());
Assert.assertEquals(expectedValue, keyValueProto.getStringV());
}
}
} }
// Utility methods // Utility methods

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

@ -26,6 +26,7 @@ import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.thingsboard.edge.rpc.EdgeGrpcClient; import org.thingsboard.edge.rpc.EdgeGrpcClient;
import org.thingsboard.edge.rpc.EdgeRpcClient; import org.thingsboard.edge.rpc.EdgeRpcClient;
import org.thingsboard.server.gen.edge.v1.AdminSettingsUpdateMsg;
import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg; import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg;
import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg; import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg;
import org.thingsboard.server.gen.edge.v1.CustomerUpdateMsg; import org.thingsboard.server.gen.edge.v1.CustomerUpdateMsg;
@ -167,6 +168,11 @@ public class EdgeImitator {
private ListenableFuture<List<Void>> processDownlinkMsg(DownlinkMsg downlinkMsg) { private ListenableFuture<List<Void>> processDownlinkMsg(DownlinkMsg downlinkMsg) {
List<ListenableFuture<Void>> result = new ArrayList<>(); List<ListenableFuture<Void>> result = new ArrayList<>();
if (downlinkMsg.getAdminSettingsUpdateMsgCount() > 0) {
for (AdminSettingsUpdateMsg adminSettingsUpdateMsg : downlinkMsg.getAdminSettingsUpdateMsgList()) {
result.add(saveDownlinkMsg(adminSettingsUpdateMsg));
}
}
if (downlinkMsg.getDeviceUpdateMsgCount() > 0) { if (downlinkMsg.getDeviceUpdateMsgCount() > 0) {
for (DeviceUpdateMsg deviceUpdateMsg : downlinkMsg.getDeviceUpdateMsgList()) { for (DeviceUpdateMsg deviceUpdateMsg : downlinkMsg.getDeviceUpdateMsgList()) {
result.add(saveDownlinkMsg(deviceUpdateMsg)); result.add(saveDownlinkMsg(deviceUpdateMsg));

2
application/src/test/java/org/thingsboard/server/service/ServiceSqlTestSuite.java

@ -26,7 +26,7 @@ import java.util.Arrays;
@RunWith(ClasspathSuite.class) @RunWith(ClasspathSuite.class)
@ClasspathSuite.ClassnameFilters({ @ClasspathSuite.ClassnameFilters({
"org.thingsboard.server.service.resource.*Test", "org.thingsboard.server.service.resource.sql.*Test",
}) })
public class ServiceSqlTestSuite { public class ServiceSqlTestSuite {

8
application/src/test/java/org/thingsboard/server/service/cluster/routing/HashPartitionServiceTest.java

@ -21,11 +21,13 @@ import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.util.ReflectionTestUtils;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.queue.QueueService;
import org.thingsboard.server.queue.discovery.HashPartitionService; import org.thingsboard.server.queue.discovery.HashPartitionService;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
@ -57,6 +59,7 @@ public class HashPartitionServiceTest {
private TenantRoutingInfoService routingInfoService; private TenantRoutingInfoService routingInfoService;
private ApplicationEventPublisher applicationEventPublisher; private ApplicationEventPublisher applicationEventPublisher;
private TbQueueRuleEngineSettings ruleEngineSettings; private TbQueueRuleEngineSettings ruleEngineSettings;
private QueueService queueService;
private String hashFunctionName = "sha256"; private String hashFunctionName = "sha256";
@ -67,10 +70,12 @@ public class HashPartitionServiceTest {
applicationEventPublisher = mock(ApplicationEventPublisher.class); applicationEventPublisher = mock(ApplicationEventPublisher.class);
routingInfoService = mock(TenantRoutingInfoService.class); routingInfoService = mock(TenantRoutingInfoService.class);
ruleEngineSettings = mock(TbQueueRuleEngineSettings.class); ruleEngineSettings = mock(TbQueueRuleEngineSettings.class);
queueService = mock(QueueService.class);
clusterRoutingService = new HashPartitionService(discoveryService, clusterRoutingService = new HashPartitionService(discoveryService,
routingInfoService, routingInfoService,
applicationEventPublisher, applicationEventPublisher,
ruleEngineSettings ruleEngineSettings,
queueService
); );
when(ruleEngineSettings.getQueues()).thenReturn(Collections.emptyList()); when(ruleEngineSettings.getQueues()).thenReturn(Collections.emptyList());
ReflectionTestUtils.setField(clusterRoutingService, "coreTopic", "tb.core"); ReflectionTestUtils.setField(clusterRoutingService, "coreTopic", "tb.core");
@ -82,6 +87,7 @@ public class HashPartitionServiceTest {
.setTenantIdLSB(TenantId.NULL_UUID.getLeastSignificantBits()) .setTenantIdLSB(TenantId.NULL_UUID.getLeastSignificantBits())
.addAllServiceTypes(Collections.singletonList(ServiceType.TB_CORE.name())) .addAllServiceTypes(Collections.singletonList(ServiceType.TB_CORE.name()))
.build(); .build();
// when(queueService.resolve(Mockito.any(), Mockito.anyString())).thenAnswer(i -> i.getArguments()[1]);
// when(discoveryService.getServiceInfo()).thenReturn(currentServer); // when(discoveryService.getServiceInfo()).thenReturn(currentServer);
List<TransportProtos.ServiceInfo> otherServers = new ArrayList<>(); List<TransportProtos.ServiceInfo> otherServers = new ArrayList<>();
for (int i = 1; i < SERVER_COUNT; i++) { for (int i = 1; i < SERVER_COUNT; i++) {

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

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.service.resource; package org.thingsboard.server.service.resource.sql;
import com.datastax.oss.driver.api.core.uuid.Uuids; import com.datastax.oss.driver.api.core.uuid.Uuids;
import org.junit.After; import org.junit.After;
@ -40,6 +40,7 @@ import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileCon
import org.thingsboard.server.controller.AbstractControllerTest; import org.thingsboard.server.controller.AbstractControllerTest;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.service.resource.TbResourceService;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Base64; import java.util.Base64;

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

@ -27,7 +27,7 @@ import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;

2
application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java

@ -33,7 +33,7 @@ import java.util.Arrays;
"org.thingsboard.server.transport.*.attributes.request.sql.*Test", "org.thingsboard.server.transport.*.attributes.request.sql.*Test",
"org.thingsboard.server.transport.*.claim.sql.*Test", "org.thingsboard.server.transport.*.claim.sql.*Test",
"org.thingsboard.server.transport.*.provision.sql.*Test", "org.thingsboard.server.transport.*.provision.sql.*Test",
"org.thingsboard.server.transport.lwm2m.*Test" "org.thingsboard.server.transport.lwm2m.sql.*Test"
}) })
public class TransportSqlTestSuite { public class TransportSqlTestSuite {

8
application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/AbstractCoapAttributesUpdatesIntegrationTest.java

@ -109,7 +109,7 @@ public abstract class AbstractCoapAttributesUpdatesIntegrationTest extends Abstr
protected void validateCurrentStateAttributesResponse(TestCoapCallback callback) throws InvalidProtocolBufferException { protected void validateCurrentStateAttributesResponse(TestCoapCallback callback) throws InvalidProtocolBufferException {
assertNotNull(callback.getPayloadBytes()); assertNotNull(callback.getPayloadBytes());
assertNotNull(callback.getObserve()); assertNotNull(callback.getObserve());
assertEquals(callback.getResponseCode(), CoAP.ResponseCode._UNKNOWN_SUCCESS_CODE); assertEquals(CoAP.ResponseCode.CONTENT, callback.getResponseCode());
assertEquals(0, callback.getObserve().intValue()); assertEquals(0, callback.getObserve().intValue());
String response = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8); String response = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8);
assertEquals(JacksonUtil.toJsonNode(POST_ATTRIBUTES_PAYLOAD_ON_CURRENT_STATE_NOTIFICATION), JacksonUtil.toJsonNode(response)); assertEquals(JacksonUtil.toJsonNode(POST_ATTRIBUTES_PAYLOAD_ON_CURRENT_STATE_NOTIFICATION), JacksonUtil.toJsonNode(response));
@ -118,7 +118,7 @@ public abstract class AbstractCoapAttributesUpdatesIntegrationTest extends Abstr
protected void validateEmptyCurrentStateAttributesResponse(TestCoapCallback callback) throws InvalidProtocolBufferException { protected void validateEmptyCurrentStateAttributesResponse(TestCoapCallback callback) throws InvalidProtocolBufferException {
assertNotNull(callback.getPayloadBytes()); assertNotNull(callback.getPayloadBytes());
assertNotNull(callback.getObserve()); assertNotNull(callback.getObserve());
assertEquals(callback.getResponseCode(), CoAP.ResponseCode._UNKNOWN_SUCCESS_CODE); assertEquals(CoAP.ResponseCode.CONTENT, callback.getResponseCode());
assertEquals(0, callback.getObserve().intValue()); assertEquals(0, callback.getObserve().intValue());
String response = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8); String response = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8);
assertEquals("{}", response); assertEquals("{}", response);
@ -127,7 +127,7 @@ public abstract class AbstractCoapAttributesUpdatesIntegrationTest extends Abstr
protected void validateUpdateAttributesResponse(TestCoapCallback callback) throws InvalidProtocolBufferException { protected void validateUpdateAttributesResponse(TestCoapCallback callback) throws InvalidProtocolBufferException {
assertNotNull(callback.getPayloadBytes()); assertNotNull(callback.getPayloadBytes());
assertNotNull(callback.getObserve()); assertNotNull(callback.getObserve());
assertEquals(callback.getResponseCode(), CoAP.ResponseCode._UNKNOWN_SUCCESS_CODE); assertEquals(CoAP.ResponseCode.CONTENT, callback.getResponseCode());
assertEquals(1, callback.getObserve().intValue()); assertEquals(1, callback.getObserve().intValue());
String response = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8); String response = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8);
assertEquals(JacksonUtil.toJsonNode(POST_ATTRIBUTES_PAYLOAD), JacksonUtil.toJsonNode(response)); assertEquals(JacksonUtil.toJsonNode(POST_ATTRIBUTES_PAYLOAD), JacksonUtil.toJsonNode(response));
@ -136,7 +136,7 @@ public abstract class AbstractCoapAttributesUpdatesIntegrationTest extends Abstr
protected void validateDeleteAttributesResponse(TestCoapCallback callback) throws InvalidProtocolBufferException { protected void validateDeleteAttributesResponse(TestCoapCallback callback) throws InvalidProtocolBufferException {
assertNotNull(callback.getPayloadBytes()); assertNotNull(callback.getPayloadBytes());
assertNotNull(callback.getObserve()); assertNotNull(callback.getObserve());
assertEquals(callback.getResponseCode(), CoAP.ResponseCode._UNKNOWN_SUCCESS_CODE); assertEquals(CoAP.ResponseCode.CONTENT, callback.getResponseCode());
assertEquals(2, callback.getObserve().intValue()); assertEquals(2, callback.getObserve().intValue());
String response = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8); String response = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8);
assertEquals(JacksonUtil.toJsonNode(RESPONSE_ATTRIBUTES_PAYLOAD_DELETED), JacksonUtil.toJsonNode(response)); assertEquals(JacksonUtil.toJsonNode(RESPONSE_ATTRIBUTES_PAYLOAD_DELETED), JacksonUtil.toJsonNode(response));

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

Loading…
Cancel
Save