Browse Source

Merge branch 'master' of github.com:thingsboard/thingsboard

pull/1227/head
ShvaykaD 8 years ago
parent
commit
ae91c85e1b
  1. 1
      .gitignore
  2. 1
      .travis.yml
  3. 46
      application/pom.xml
  4. 6
      application/src/main/data/json/system/widget_bundles/cards.json
  5. 8
      application/src/main/data/json/system/widget_bundles/charts.json
  6. 81
      application/src/main/data/upgrade/2.1.1/schema_update.cql
  7. 31
      application/src/main/data/upgrade/2.1.1/schema_update.sql
  8. 110
      application/src/main/data/upgrade/2.1.2/schema_update.cql
  9. 32
      application/src/main/data/upgrade/2.1.2/schema_update.sql
  10. 17
      application/src/main/data/upgrade/2.2.0/schema_update.sql
  11. 34
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  12. 100
      application/src/main/java/org/thingsboard/server/actors/app/AppActor.java
  13. 56
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java
  14. 39
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActorCreator.java
  15. 681
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
  16. 40
      application/src/main/java/org/thingsboard/server/actors/device/PendingSessionMsgData.java
  17. 8
      application/src/main/java/org/thingsboard/server/actors/device/SessionInfo.java
  18. 26
      application/src/main/java/org/thingsboard/server/actors/device/SessionInfoMetaData.java
  19. 19
      application/src/main/java/org/thingsboard/server/actors/device/SessionTimeoutCheckMsg.java
  20. 12
      application/src/main/java/org/thingsboard/server/actors/device/ToServerRpcRequestMetadata.java
  21. 12
      application/src/main/java/org/thingsboard/server/actors/rpc/BasicRpcSessionListener.java
  22. 32
      application/src/main/java/org/thingsboard/server/actors/rpc/RpcManagerActor.java
  23. 5
      application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionActor.java
  24. 23
      application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
  25. 5
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActor.java
  26. 82
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java
  27. 10
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActor.java
  28. 13
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java
  29. 1
      application/src/main/java/org/thingsboard/server/actors/service/ActorService.java
  30. 16
      application/src/main/java/org/thingsboard/server/actors/service/ComponentActor.java
  31. 22
      application/src/main/java/org/thingsboard/server/actors/service/ContextAwareActor.java
  32. 34
      application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java
  33. 156
      application/src/main/java/org/thingsboard/server/actors/session/ASyncMsgProcessor.java
  34. 122
      application/src/main/java/org/thingsboard/server/actors/session/AbstractSessionActorMsgProcessor.java
  35. 143
      application/src/main/java/org/thingsboard/server/actors/session/SessionActor.java
  36. 180
      application/src/main/java/org/thingsboard/server/actors/session/SessionManagerActor.java
  37. 95
      application/src/main/java/org/thingsboard/server/actors/session/SyncMsgProcessor.java
  38. 34
      application/src/main/java/org/thingsboard/server/actors/shared/AbstractContextAwareMsgProcessor.java
  39. 28
      application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java
  40. 9
      application/src/main/java/org/thingsboard/server/actors/shared/EntityActorsManager.java
  41. 2
      application/src/main/java/org/thingsboard/server/actors/shared/rulechain/RuleChainManager.java
  42. 13
      application/src/main/java/org/thingsboard/server/actors/stats/StatsActor.java
  43. 84
      application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
  44. 2
      application/src/main/java/org/thingsboard/server/controller/AlarmController.java
  45. 80
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  46. 13
      application/src/main/java/org/thingsboard/server/controller/DashboardController.java
  47. 336
      application/src/main/java/org/thingsboard/server/controller/EntityViewController.java
  48. 6
      application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
  49. 80
      application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
  50. 3
      application/src/main/java/org/thingsboard/server/controller/TenantController.java
  51. 30
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  52. 6
      application/src/main/java/org/thingsboard/server/service/cluster/discovery/CurrentServerInstanceService.java
  53. 4
      application/src/main/java/org/thingsboard/server/service/cluster/discovery/DiscoveryService.java
  54. 9
      application/src/main/java/org/thingsboard/server/service/cluster/discovery/DummyDiscoveryService.java
  55. 7
      application/src/main/java/org/thingsboard/server/service/cluster/discovery/ServerInstance.java
  56. 130
      application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java
  57. 9
      application/src/main/java/org/thingsboard/server/service/cluster/routing/ClusterRoutingService.java
  58. 38
      application/src/main/java/org/thingsboard/server/service/cluster/routing/ConsistentClusterRoutingService.java
  59. 63
      application/src/main/java/org/thingsboard/server/service/cluster/routing/ConsistentHashCircle.java
  60. 16
      application/src/main/java/org/thingsboard/server/service/cluster/rpc/GrpcSession.java
  61. 39
      application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java
  62. 19
      application/src/main/java/org/thingsboard/server/service/install/CassandraAbstractDatabaseSchemaService.java
  63. 50
      application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java
  64. 30
      application/src/main/java/org/thingsboard/server/service/install/CassandraEntityDatabaseSchemaService.java
  65. 30
      application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseSchemaService.java
  66. 9
      application/src/main/java/org/thingsboard/server/service/install/DatabaseHelper.java
  67. 7
      application/src/main/java/org/thingsboard/server/service/install/EntityDatabaseSchemaService.java
  68. 19
      application/src/main/java/org/thingsboard/server/service/install/SqlAbstractDatabaseSchemaService.java
  69. 50
      application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
  70. 30
      application/src/main/java/org/thingsboard/server/service/install/SqlEntityDatabaseSchemaService.java
  71. 20
      application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseSchemaService.java
  72. 5
      application/src/main/java/org/thingsboard/server/service/install/TsDatabaseSchemaService.java
  73. 4
      application/src/main/java/org/thingsboard/server/service/install/cql/CassandraDbHelper.java
  74. 111
      application/src/main/java/org/thingsboard/server/service/queue/DefaultMsgQueueService.java
  75. 32
      application/src/main/java/org/thingsboard/server/service/queue/MsgQueueService.java
  76. 68
      application/src/main/java/org/thingsboard/server/service/rpc/DefaultDeviceRpcService.java
  77. 10
      application/src/main/java/org/thingsboard/server/service/rpc/DeviceRpcService.java
  78. 2
      application/src/main/java/org/thingsboard/server/service/rpc/FromDeviceRpcResponse.java
  79. 3
      application/src/main/java/org/thingsboard/server/service/rpc/ToServerRpcResponseActorMsg.java
  80. 102
      application/src/main/java/org/thingsboard/server/service/script/AbstractJsInvokeService.java
  81. 109
      application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java
  82. 188
      application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsSandboxService.java
  83. 13
      application/src/main/java/org/thingsboard/server/service/script/JsInvokeRequest.java
  84. 29
      application/src/main/java/org/thingsboard/server/service/script/JsInvokeResponse.java
  85. 3
      application/src/main/java/org/thingsboard/server/service/script/JsInvokeService.java
  86. 12
      application/src/main/java/org/thingsboard/server/service/script/NashornJsInvokeService.java
  87. 211
      application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java
  88. 21
      application/src/main/java/org/thingsboard/server/service/script/RemoteJsRequestEncoder.java
  89. 32
      application/src/main/java/org/thingsboard/server/service/script/RemoteJsResponseDecoder.java
  90. 19
      application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java
  91. 2
      application/src/main/java/org/thingsboard/server/service/script/RuleNodeScriptFactory.java
  92. 31
      application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java
  93. 47
      application/src/main/java/org/thingsboard/server/service/security/ValidationCallback.java
  94. 8
      application/src/main/java/org/thingsboard/server/service/security/model/token/RawAccessJwtToken.java
  95. 50
      application/src/main/java/org/thingsboard/server/service/session/DefaultDeviceSessionCacheService.java
  96. 20
      application/src/main/java/org/thingsboard/server/service/session/DeviceSessionCacheService.java
  97. 3
      application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java
  98. 50
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java
  99. 14
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java
  100. 6
      application/src/main/java/org/thingsboard/server/service/telemetry/sub/Subscription.java

1
.gitignore

@ -32,3 +32,4 @@ pom.xml.versionsBackup
**/Californium.properties
**/.env
.instance_id
rebuild-docker.sh

1
.travis.yml

@ -2,6 +2,7 @@ before_install:
- sudo rm -f /etc/mavenrc
- export M2_HOME=/usr/local/maven
- export MAVEN_OPTS="-Dmaven.repo.local=$HOME/.m2/repository -Xms1024m -Xmx3072m"
- export HTTP_LOG_CONTROLLER_ERROR_STACK_TRACE=false
jdk:
- oraclejdk8
language: java

46
application/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>2.1.1-SNAPSHOT</version>
<version>2.2.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>application</artifactId>
@ -57,25 +57,29 @@
<artifactId>rule-engine-components</artifactId>
</dependency>
<dependency>
<groupId>org.thingsboard.common</groupId>
<artifactId>transport</artifactId>
<groupId>org.thingsboard.common.transport</groupId>
<artifactId>transport-api</artifactId>
</dependency>
<dependency>
<groupId>org.thingsboard.transport</groupId>
<artifactId>http</artifactId>
<groupId>org.thingsboard.common.transport</groupId>
<artifactId>mqtt</artifactId>
</dependency>
<dependency>
<groupId>org.thingsboard.transport</groupId>
<artifactId>coap</artifactId>
<groupId>org.thingsboard.common.transport</groupId>
<artifactId>http</artifactId>
</dependency>
<dependency>
<groupId>org.thingsboard.transport</groupId>
<artifactId>mqtt</artifactId>
<groupId>org.thingsboard.common.transport</groupId>
<artifactId>coap</artifactId>
</dependency>
<dependency>
<groupId>org.thingsboard</groupId>
<artifactId>dao</artifactId>
</dependency>
<dependency>
<groupId>org.thingsboard.common</groupId>
<artifactId>queue</artifactId>
</dependency>
<dependency>
<groupId>org.thingsboard</groupId>
<artifactId>dao</artifactId>
@ -538,7 +542,8 @@
<args>
<arg>-PprojectBuildDir=${project.build.directory}</arg>
<arg>-PprojectVersion=${project.version}</arg>
<arg>-PmainJar=${project.build.directory}/${project.build.finalName}-boot.${project.packaging}</arg>
<arg>-PmainJar=${project.build.directory}/${project.build.finalName}-boot.${project.packaging}
</arg>
<arg>-PpkgName=${pkg.name}</arg>
<arg>-PpkgInstallFolder=${pkg.installFolder}</arg>
<arg>-PpkgLogFolder=${pkg.unixLogFolder}</arg>
@ -572,6 +577,27 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-install-plugin</artifactId>
<configuration>
<file>${project.build.directory}/${pkg.name}.deb</file>
<artifactId>${project.artifactId}</artifactId>
<groupId>${project.groupId}</groupId>
<version>${project.version}</version>
<classifier>deb</classifier>
<packaging>deb</packaging>
</configuration>
<executions>
<execution>
<id>install-deb</id>
<phase>package</phase>
<goals>
<goal>install-file</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>

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

File diff suppressed because one or more lines are too long

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

@ -35,7 +35,7 @@
"resources": [],
"templateHtml": "",
"templateCss": ".legend {\n font-size: 13px;\n line-height: 10px;\n}\n\n.legend table { \n border-spacing: 0px;\n border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n cursor: crosshair; \n}\n\n",
"controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.settingsSchema;\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(true);\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n",
"controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.settingsSchema('graph');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(true);\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"legend\":{\"show\":true,\"position\":\"nw\",\"backgroundColor\":\"#f0f0f0\",\"backgroundOpacity\":0.85,\"labelBoxBorderColor\":\"rgba(1, 1, 1, 0.45)\"},\"decimals\":1,\"stack\":false,\"tooltipIndividual\":false},\"title\":\"Timeseries - Flot\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null}"
@ -147,10 +147,10 @@
"resources": [],
"templateHtml": "",
"templateCss": ".legend {\n font-size: 13px;\n line-height: 10px;\n}\n\n.legend table { \n border-spacing: 0px;\n border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n cursor: crosshair; \n}\n\n",
"controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx, 'bar'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.settingsSchema;\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(false);\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n",
"controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx, 'bar'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.settingsSchema('bar');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(false);\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":false,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":false,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000},\"aggregation\":{\"limit\":200,\"type\":\"AVG\"}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"legend\":{\"show\":true,\"position\":\"nw\",\"backgroundColor\":\"#f0f0f0\",\"backgroundOpacity\":0.85,\"labelBoxBorderColor\":\"rgba(1, 1, 1, 0.45)\"},\"decimals\":1,\"stack\":true,\"tooltipIndividual\":false},\"title\":\"Timeseries Bars - Flot\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null}"
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":false,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":false,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000},\"aggregation\":{\"limit\":200,\"type\":\"AVG\"}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"stack\":true,\"tooltipIndividual\":false,\"defaultBarWidth\":600},\"title\":\"Timeseries Bars - Flot\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":true,\"actions\":{}}"
}
},
{
@ -163,7 +163,7 @@
"resources": [],
"templateHtml": "",
"templateCss": ".legend {\n font-size: 13px;\n line-height: 10px;\n}\n\n.legend table { \n border-spacing: 0px;\n border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n cursor: crosshair; \n}\n\n",
"controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx, 'state'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.typeParameters = function() {\n return {\n stateData: true\n };\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.settingsSchema;\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(true);\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n",
"controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx, 'state'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.typeParameters = function() {\n return {\n stateData: true\n };\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.settingsSchema('graph');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(true);\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 1\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false,\"axisPosition\":\"left\",\"showSeparateAxis\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"return Math.random() > 0.5 ? 1 : 0;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 2\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false,\"axisPosition\":\"left\"},\"_hash\":0.12775350966079668,\"funcBody\":\"return Math.random() <= 0.5 ? 1 : 0;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\",\"ticksFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"stack\":false,\"tooltipIndividual\":false,\"tooltipValueFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\",\"smoothLines\":false},\"title\":\"State Chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":true,\"actions\":{},\"legendConfig\":{\"position\":\"bottom\",\"showMin\":false,\"showMax\":false,\"showAvg\":false,\"showTotal\":false}}"

81
application/src/main/data/upgrade/2.1.1/schema_update.cql

@ -0,0 +1,81 @@
--
-- Copyright © 2016-2018 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.
--
DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_name;
DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_search_text;
DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_customer;
DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_entity_id;
DROP TABLE IF EXISTS thingsboard.entity_views;
CREATE TABLE IF NOT EXISTS thingsboard.entity_views (
id timeuuid,
entity_id timeuuid,
entity_type text,
tenant_id timeuuid,
customer_id timeuuid,
name text,
keys text,
start_ts bigint,
end_ts bigint,
search_text text,
additional_info text,
PRIMARY KEY (id, entity_id, tenant_id, customer_id)
);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_name AS
SELECT *
from thingsboard.entity_views
WHERE tenant_id IS NOT NULL
AND entity_id IS NOT NULL
AND customer_id IS NOT NULL
AND name IS NOT NULL
AND id IS NOT NULL
PRIMARY KEY (tenant_id, name, id, customer_id, entity_id)
WITH CLUSTERING ORDER BY (name ASC, id DESC, customer_id DESC);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_search_text AS
SELECT *
from thingsboard.entity_views
WHERE tenant_id IS NOT NULL
AND entity_id IS NOT NULL
AND customer_id IS NOT NULL
AND search_text IS NOT NULL
AND id IS NOT NULL
PRIMARY KEY (tenant_id, search_text, id, customer_id, entity_id)
WITH CLUSTERING ORDER BY (search_text ASC, id DESC, customer_id DESC);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_customer AS
SELECT *
from thingsboard.entity_views
WHERE tenant_id IS NOT NULL
AND customer_id IS NOT NULL
AND entity_id IS NOT NULL
AND search_text IS NOT NULL
AND id IS NOT NULL
PRIMARY KEY (tenant_id, customer_id, search_text, id, entity_id)
WITH CLUSTERING ORDER BY (customer_id DESC, search_text ASC, id DESC);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_entity_id AS
SELECT *
from thingsboard.entity_views
WHERE tenant_id IS NOT NULL
AND customer_id IS NOT NULL
AND entity_id IS NOT NULL
AND search_text IS NOT NULL
AND id IS NOT NULL
PRIMARY KEY (tenant_id, entity_id, customer_id, search_text, id)
WITH CLUSTERING ORDER BY (entity_id DESC, customer_id DESC, search_text ASC, id DESC);

31
application/src/main/data/upgrade/2.1.1/schema_update.sql

@ -0,0 +1,31 @@
--
-- Copyright © 2016-2018 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.
--
DROP TABLE IF EXISTS entity_views;
CREATE TABLE IF NOT EXISTS entity_views (
id varchar(31) NOT NULL CONSTRAINT entity_view_pkey PRIMARY KEY,
entity_id varchar(31),
entity_type varchar(255),
tenant_id varchar(31),
customer_id varchar(31),
name varchar(255),
keys varchar(255),
start_ts bigint,
end_ts bigint,
search_text varchar(255),
additional_info varchar
);

110
application/src/main/data/upgrade/2.1.2/schema_update.cql

@ -0,0 +1,110 @@
--
-- Copyright © 2016-2018 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.
--
DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_name;
DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_search_text;
DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_customer;
DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_entity_id;
DROP TABLE IF EXISTS thingsboard.entity_views;
CREATE TABLE IF NOT EXISTS thingsboard.entity_view (
id timeuuid,
entity_id timeuuid,
entity_type text,
tenant_id timeuuid,
customer_id timeuuid,
name text,
type text,
keys text,
start_ts bigint,
end_ts bigint,
search_text text,
additional_info text,
PRIMARY KEY (id, entity_id, tenant_id, customer_id, type)
);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_name AS
SELECT *
from thingsboard.entity_view
WHERE tenant_id IS NOT NULL
AND entity_id IS NOT NULL
AND customer_id IS NOT NULL
AND type IS NOT NULL
AND name IS NOT NULL
AND id IS NOT NULL
PRIMARY KEY (tenant_id, name, id, customer_id, entity_id, type)
WITH CLUSTERING ORDER BY (name ASC, id DESC, customer_id DESC);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_search_text AS
SELECT *
from thingsboard.entity_view
WHERE tenant_id IS NOT NULL
AND entity_id IS NOT NULL
AND customer_id IS NOT NULL
AND type IS NOT NULL
AND search_text IS NOT NULL
AND id IS NOT NULL
PRIMARY KEY (tenant_id, search_text, id, customer_id, entity_id, type)
WITH CLUSTERING ORDER BY (search_text ASC, id DESC, customer_id DESC);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_by_type_and_search_text AS
SELECT *
from thingsboard.entity_view
WHERE tenant_id IS NOT NULL
AND entity_id IS NOT NULL
AND customer_id IS NOT NULL
AND type IS NOT NULL
AND search_text IS NOT NULL
AND id IS NOT NULL
PRIMARY KEY (tenant_id, type, search_text, id, customer_id, entity_id)
WITH CLUSTERING ORDER BY (type ASC, search_text ASC, id DESC, customer_id DESC);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_customer AS
SELECT *
from thingsboard.entity_view
WHERE tenant_id IS NOT NULL
AND customer_id IS NOT NULL
AND entity_id IS NOT NULL
AND type IS NOT NULL
AND search_text IS NOT NULL
AND id IS NOT NULL
PRIMARY KEY (tenant_id, customer_id, search_text, id, entity_id, type)
WITH CLUSTERING ORDER BY (customer_id DESC, search_text ASC, id DESC);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_customer_and_type AS
SELECT *
from thingsboard.entity_view
WHERE tenant_id IS NOT NULL
AND customer_id IS NOT NULL
AND entity_id IS NOT NULL
AND type IS NOT NULL
AND search_text IS NOT NULL
AND id IS NOT NULL
PRIMARY KEY (tenant_id, type, customer_id, search_text, id, entity_id)
WITH CLUSTERING ORDER BY (type ASC, customer_id DESC, search_text ASC, id DESC);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_entity_id AS
SELECT *
from thingsboard.entity_view
WHERE tenant_id IS NOT NULL
AND customer_id IS NOT NULL
AND entity_id IS NOT NULL
AND type IS NOT NULL
AND search_text IS NOT NULL
AND id IS NOT NULL
PRIMARY KEY (tenant_id, entity_id, customer_id, search_text, id, type)
WITH CLUSTERING ORDER BY (entity_id DESC, customer_id DESC, search_text ASC, id DESC);

32
application/src/main/data/upgrade/2.1.2/schema_update.sql

@ -0,0 +1,32 @@
--
-- Copyright © 2016-2018 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.
--
DROP TABLE IF EXISTS entity_views;
CREATE TABLE IF NOT EXISTS entity_view (
id varchar(31) NOT NULL CONSTRAINT entity_view_pkey PRIMARY KEY,
entity_id varchar(31),
entity_type varchar(255),
tenant_id varchar(31),
customer_id varchar(31),
type varchar(255),
name varchar(255),
keys varchar(255),
start_ts bigint,
end_ts bigint,
search_text varchar(255),
additional_info varchar
);

17
application/src/main/data/upgrade/2.2.0/schema_update.sql

@ -0,0 +1,17 @@
--
-- Copyright © 2016-2018 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.
--
ALTER TABLE component_descriptor ADD UNIQUE (clazz);

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

@ -31,6 +31,7 @@ import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.server.actors.service.ActorService;
@ -48,6 +49,7 @@ import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.audit.AuditLogService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService;
@ -62,12 +64,13 @@ import org.thingsboard.server.service.encoding.DataDecodingEncodingService;
import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.executors.ExternalCallExecutorService;
import org.thingsboard.server.service.mail.MailExecutorService;
import org.thingsboard.server.service.queue.MsgQueueService;
import org.thingsboard.server.service.rpc.DeviceRpcService;
import org.thingsboard.server.service.script.JsExecutorService;
import org.thingsboard.server.service.script.JsSandboxService;
import org.thingsboard.server.service.script.JsInvokeService;
import org.thingsboard.server.service.session.DeviceSessionCacheService;
import org.thingsboard.server.service.state.DeviceStateService;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import org.thingsboard.server.service.transport.RuleEngineTransportService;
import javax.annotation.Nullable;
import java.io.IOException;
@ -159,6 +162,10 @@ public class ActorSystemContext {
@Getter
private AuditLogService auditLogService;
@Autowired
@Getter
private EntityViewService entityViewService;
@Autowired
@Getter
private TelemetrySubscriptionService tsSubService;
@ -169,7 +176,7 @@ public class ActorSystemContext {
@Autowired
@Getter
private JsSandboxService jsSandbox;
private JsInvokeService jsSandbox;
@Autowired
@Getter
@ -193,11 +200,16 @@ public class ActorSystemContext {
@Autowired
@Getter
private MsgQueueService msgQueueService;
private DeviceStateService deviceStateService;
@Autowired
@Getter
private DeviceSessionCacheService deviceSessionCacheService;
@Lazy
@Autowired
@Getter
private DeviceStateService deviceStateService;
private RuleEngineTransportService ruleEngineTransportService;
@Value("${cluster.partition_id}")
@Getter
@ -247,17 +259,21 @@ public class ActorSystemContext {
@Getter
private boolean allowSystemMailService;
@Value("${transport.sessions.inactivity_timeout}")
@Getter
@Setter
private ActorSystem actorSystem;
private long sessionInactivityTimeout;
@Value("${transport.sessions.report_timeout}")
@Getter
private long sessionReportTimeout;
@Getter
@Setter
private ActorRef appActor;
private ActorSystem actorSystem;
@Getter
@Setter
private ActorRef sessionManagerActor;
private ActorRef appActor;
@Getter
@Setter

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

@ -25,21 +25,24 @@ import akka.actor.Terminated;
import akka.event.Logging;
import akka.event.LoggingAdapter;
import akka.japi.Function;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.ruleChain.RuleChainManagerActor;
import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.actors.shared.rulechain.SystemRuleChainManager;
import org.thingsboard.server.actors.tenant.TenantActor;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.aware.TenantAwareMsg;
import org.thingsboard.server.common.msg.cluster.SendToClusterMsg;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.core.BasicActorSystemToDeviceSessionActorMsg;
import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
import org.thingsboard.server.dao.model.ModelConstants;
@ -52,16 +55,14 @@ import java.util.Optional;
public class AppActor extends RuleChainManagerActor {
private final LoggingAdapter logger = Logging.getLogger(getContext().system(), this);
public static final TenantId SYSTEM_TENANT = new TenantId(ModelConstants.NULL_UUID);
private static final TenantId SYSTEM_TENANT = new TenantId(ModelConstants.NULL_UUID);
private final TenantService tenantService;
private final Map<TenantId, ActorRef> tenantActors;
private final BiMap<TenantId, ActorRef> tenantActors;
private AppActor(ActorSystemContext systemContext) {
super(systemContext, new SystemRuleChainManager(systemContext));
this.tenantService = systemContext.getTenantService();
this.tenantActors = new HashMap<>();
this.tenantActors = HashBiMap.create();
}
@Override
@ -71,22 +72,20 @@ public class AppActor extends RuleChainManagerActor {
@Override
public void preStart() {
logger.info("Starting main system actor.");
log.info("Starting main system actor.");
try {
initRuleChains();
if (systemContext.isTenantComponentsInitEnabled()) {
PageDataIterable<Tenant> tenantIterator = new PageDataIterable<>(tenantService::findTenants, ENTITY_PACK_LIMIT);
for (Tenant tenant : tenantIterator) {
logger.debug("[{}] Creating tenant actor", tenant.getId());
log.debug("[{}] Creating tenant actor", tenant.getId());
getOrCreateTenantActor(tenant.getId());
logger.debug("Tenant actor created.");
log.debug("Tenant actor created.");
}
}
logger.info("Main system actor started.");
log.info("Main system actor started.");
} catch (Exception e) {
logger.error(e, "Unknown failure");
log.warn("Unknown failure", e);
}
}
@ -105,7 +104,7 @@ public class AppActor extends RuleChainManagerActor {
case SERVICE_TO_RULE_ENGINE_MSG:
onServiceToRuleEngineMsg((ServiceToRuleEngineMsg) msg);
break;
case DEVICE_SESSION_TO_DEVICE_ACTOR_MSG:
case TRANSPORT_TO_DEVICE_ACTOR_MSG:
case DEVICE_ATTRIBUTES_UPDATE_TO_DEVICE_ACTOR_MSG:
case DEVICE_CREDENTIALS_UPDATE_TO_DEVICE_ACTOR_MSG:
case DEVICE_NAME_OR_TYPE_UPDATE_TO_DEVICE_ACTOR_MSG:
@ -114,19 +113,12 @@ public class AppActor extends RuleChainManagerActor {
case REMOTE_TO_RULE_CHAIN_TELL_NEXT_MSG:
onToDeviceActorMsg((TenantAwareMsg) msg);
break;
case ACTOR_SYSTEM_TO_DEVICE_SESSION_ACTOR_MSG:
onToDeviceSessionMsg((BasicActorSystemToDeviceSessionActorMsg) msg);
break;
default:
return false;
}
return true;
}
private void onToDeviceSessionMsg(BasicActorSystemToDeviceSessionActorMsg msg) {
systemContext.getSessionManagerActor().tell(msg, self());
}
private void onPossibleClusterMsg(SendToClusterMsg msg) {
Optional<ServerAddress> address = systemContext.getRoutingService().resolveById(msg.getEntityId());
if (address.isPresent()) {
@ -139,7 +131,8 @@ public class AppActor extends RuleChainManagerActor {
private void onServiceToRuleEngineMsg(ServiceToRuleEngineMsg msg) {
if (SYSTEM_TENANT.equals(msg.getTenantId())) {
//TODO: ashvayka handle this.
// this may be a notification about system entities created.
// log.warn("[{}] Invalid service to rule engine msg called. System messages are not supported yet: {}", SYSTEM_TENANT, msg);
} else {
getOrCreateTenantActor(msg.getTenantId()).tell(msg, self());
}
@ -152,16 +145,26 @@ public class AppActor extends RuleChainManagerActor {
}
private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) {
ActorRef target;
ActorRef target = null;
if (SYSTEM_TENANT.equals(msg.getTenantId())) {
target = getEntityActorRef(msg.getEntityId());
} else {
target = getOrCreateTenantActor(msg.getTenantId());
if (msg.getEntityId().getEntityType() == EntityType.TENANT
&& msg.getEvent() == ComponentLifecycleEvent.DELETED) {
log.debug("[{}] Handling tenant deleted notification: {}", msg.getTenantId(), msg);
ActorRef tenantActor = tenantActors.remove(new TenantId(msg.getEntityId().getId()));
if (tenantActor != null) {
log.debug("[{}] Deleting tenant actor: {}", msg.getTenantId(), tenantActor);
context().stop(tenantActor);
}
} else {
target = getOrCreateTenantActor(msg.getTenantId());
}
}
if (target != null) {
target.tell(msg, ActorRef.noSender());
} else {
logger.debug("Invalid component lifecycle msg: {}", msg);
log.debug("[{}] Invalid component lifecycle msg: {}", msg.getTenantId(), msg);
}
}
@ -169,25 +172,25 @@ public class AppActor extends RuleChainManagerActor {
getOrCreateTenantActor(msg.getTenantId()).tell(msg, ActorRef.noSender());
}
private void processDeviceMsg(DeviceToDeviceActorMsg deviceToDeviceActorMsg) {
TenantId tenantId = deviceToDeviceActorMsg.getTenantId();
ActorRef tenantActor = getOrCreateTenantActor(tenantId);
if (deviceToDeviceActorMsg.getPayload().getMsgType().requiresRulesProcessing()) {
// tenantActor.tell(new RuleChainDeviceMsg(deviceToDeviceActorMsg, ruleManager.getRuleChain(this.context())), context().self());
} else {
tenantActor.tell(deviceToDeviceActorMsg, context().self());
}
}
private ActorRef getOrCreateTenantActor(TenantId tenantId) {
return tenantActors.computeIfAbsent(tenantId, k -> context().actorOf(Props.create(new TenantActor.ActorCreator(systemContext, tenantId))
.withDispatcher(DefaultActorService.CORE_DISPATCHER_NAME), tenantId.toString()));
return tenantActors.computeIfAbsent(tenantId, k -> {
log.debug("[{}] Creating tenant actor.", tenantId);
ActorRef tenantActor = context().actorOf(Props.create(new TenantActor.ActorCreator(systemContext, tenantId))
.withDispatcher(DefaultActorService.CORE_DISPATCHER_NAME), tenantId.toString());
context().watch(tenantActor);
log.debug("[{}] Created tenant actor: {}.", tenantId, tenantActor);
return tenantActor;
});
}
private void processTermination(Terminated message) {
@Override
protected void processTermination(Terminated message) {
ActorRef terminated = message.actor();
if (terminated instanceof LocalActorRef) {
logger.debug("Removed actor: {}", terminated);
boolean removed = tenantActors.inverse().remove(terminated) != null;
if (removed) {
log.debug("[{}] Removed actor:", terminated);
}
} else {
throw new IllegalStateException("Remote actors are not supported!");
}
@ -201,20 +204,17 @@ public class AppActor extends RuleChainManagerActor {
}
@Override
public AppActor create() throws Exception {
public AppActor create() {
return new AppActor(context);
}
}
private final SupervisorStrategy strategy = new OneForOneStrategy(3, Duration.create("1 minute"), new Function<Throwable, Directive>() {
@Override
public Directive apply(Throwable t) {
logger.error(t, "Unknown failure");
if (t instanceof RuntimeException) {
return SupervisorStrategy.restart();
} else {
return SupervisorStrategy.stop();
}
private final SupervisorStrategy strategy = new OneForOneStrategy(3, Duration.create("1 minute"), t -> {
log.warn("Unknown failure", t);
if (t instanceof RuntimeException) {
return SupervisorStrategy.restart();
} else {
return SupervisorStrategy.stop();
}
});
}

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

@ -15,43 +15,44 @@
*/
package org.thingsboard.server.actors.device;
import akka.event.Logging;
import akka.event.LoggingAdapter;
import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg;
import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.service.ContextAwareActor;
import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
import org.thingsboard.server.common.msg.timeout.DeviceActorClientSideRpcTimeoutMsg;
import org.thingsboard.server.common.msg.timeout.DeviceActorQueueTimeoutMsg;
import org.thingsboard.server.common.msg.timeout.DeviceActorServerSideRpcTimeoutMsg;
import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg;
import org.thingsboard.server.service.rpc.ToServerRpcResponseActorMsg;
import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper;
public class DeviceActor extends ContextAwareActor {
private final LoggingAdapter logger = Logging.getLogger(getContext().system(), this);
private final DeviceActorMessageProcessor processor;
private DeviceActor(ActorSystemContext systemContext, TenantId tenantId, DeviceId deviceId) {
DeviceActor(ActorSystemContext systemContext, TenantId tenantId, DeviceId deviceId) {
super(systemContext);
this.processor = new DeviceActorMessageProcessor(systemContext, logger, tenantId, deviceId);
this.processor = new DeviceActorMessageProcessor(systemContext, tenantId, deviceId);
}
@Override
public void preStart() {
log.debug("[{}][{}] Starting device actor.", processor.tenantId, processor.deviceId);
try {
processor.initSessionTimeout(context());
log.debug("[{}][{}] Device actor started.", processor.tenantId, processor.deviceId);
} catch (Exception e) {
log.warn("[{}][{}] Unknown failure", processor.tenantId, processor.deviceId, e);
}
}
@Override
protected boolean process(TbActorMsg msg) {
switch (msg.getMsgType()) {
case CLUSTER_EVENT_MSG:
processor.processClusterEventMsg((ClusterEventMsg) msg);
break;
case DEVICE_SESSION_TO_DEVICE_ACTOR_MSG:
processor.process(context(), (DeviceToDeviceActorMsg) msg);
case TRANSPORT_TO_DEVICE_ACTOR_MSG:
processor.process(context(), (TransportToDeviceActorMsgWrapper) msg);
break;
case DEVICE_ATTRIBUTES_UPDATE_TO_DEVICE_ACTOR_MSG:
processor.processAttributesUpdate(context(), (DeviceAttributesEventNotificationMsg) msg);
@ -74,11 +75,8 @@ public class DeviceActor extends ContextAwareActor {
case DEVICE_ACTOR_CLIENT_SIDE_RPC_TIMEOUT_MSG:
processor.processClientSideRpcTimeout(context(), (DeviceActorClientSideRpcTimeoutMsg) msg);
break;
case DEVICE_ACTOR_QUEUE_TIMEOUT_MSG:
processor.processQueueTimeout(context(), (DeviceActorQueueTimeoutMsg) msg);
break;
case RULE_ENGINE_QUEUE_PUT_ACK_MSG:
processor.processQueueAck(context(), (RuleEngineQueuePutAckMsg) msg);
case SESSION_TIMEOUT_MSG:
processor.checkSessionsTimeout();
break;
default:
return false;
@ -86,22 +84,4 @@ public class DeviceActor extends ContextAwareActor {
return true;
}
public static class ActorCreator extends ContextBasedCreator<DeviceActor> {
private static final long serialVersionUID = 1L;
private final TenantId tenantId;
private final DeviceId deviceId;
public ActorCreator(ActorSystemContext context, TenantId tenantId, DeviceId deviceId) {
super(context);
this.tenantId = tenantId;
this.deviceId = deviceId;
}
@Override
public DeviceActor create() throws Exception {
return new DeviceActor(context, tenantId, deviceId);
}
}
}

39
application/src/main/java/org/thingsboard/server/actors/device/DeviceActorCreator.java

@ -0,0 +1,39 @@
/**
* Copyright © 2016-2018 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.actors.device;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
public class DeviceActorCreator extends ContextBasedCreator<DeviceActor> {
private static final long serialVersionUID = 1L;
private final TenantId tenantId;
private final DeviceId deviceId;
public DeviceActorCreator(ActorSystemContext context, TenantId tenantId, DeviceId deviceId) {
super(context);
this.tenantId = tenantId;
this.deviceId = deviceId;
}
@Override
public DeviceActor create() {
return new DeviceActor(context, tenantId, deviceId);
}
}

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

@ -16,7 +16,6 @@
package org.thingsboard.server.actors.device;
import akka.actor.ActorContext;
import akka.actor.ActorRef;
import akka.event.LoggingAdapter;
import com.datastax.driver.core.utils.UUIDs;
import com.google.common.util.concurrent.FutureCallback;
@ -25,6 +24,7 @@ import com.google.common.util.concurrent.ListenableFuture;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.rule.engine.api.RpcError;
import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg;
import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg;
@ -33,7 +33,6 @@ import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.SessionId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKey;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
@ -43,37 +42,34 @@ import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.core.ActorSystemToDeviceSessionActorMsg;
import org.thingsboard.server.common.msg.core.AttributesUpdateNotification;
import org.thingsboard.server.common.msg.core.AttributesUpdateRequest;
import org.thingsboard.server.common.msg.core.BasicActorSystemToDeviceSessionActorMsg;
import org.thingsboard.server.common.msg.core.BasicCommandAckResponse;
import org.thingsboard.server.common.msg.core.BasicGetAttributesResponse;
import org.thingsboard.server.common.msg.core.BasicStatusCodeResponse;
import org.thingsboard.server.common.msg.core.GetAttributesRequest;
import org.thingsboard.server.common.msg.core.RuleEngineError;
import org.thingsboard.server.common.msg.core.RuleEngineErrorMsg;
import org.thingsboard.server.common.msg.core.SessionCloseMsg;
import org.thingsboard.server.common.msg.core.SessionCloseNotification;
import org.thingsboard.server.common.msg.core.SessionOpenMsg;
import org.thingsboard.server.common.msg.core.TelemetryUploadRequest;
import org.thingsboard.server.common.msg.core.ToDeviceRpcRequestMsg;
import org.thingsboard.server.common.msg.core.ToDeviceRpcResponseMsg;
import org.thingsboard.server.common.msg.core.ToServerRpcRequestMsg;
import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
import org.thingsboard.server.common.msg.kv.BasicAttributeKVMsg;
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
import org.thingsboard.server.common.msg.session.FromDeviceMsg;
import org.thingsboard.server.common.msg.session.SessionMsgType;
import org.thingsboard.server.common.msg.session.SessionType;
import org.thingsboard.server.common.msg.session.ToDeviceMsg;
import org.thingsboard.server.common.msg.timeout.DeviceActorClientSideRpcTimeoutMsg;
import org.thingsboard.server.common.msg.timeout.DeviceActorQueueTimeoutMsg;
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.DeviceActorToTransportMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.KeyValueProto;
import org.thingsboard.server.gen.transport.TransportProtos.KeyValueType;
import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg;
import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg;
import org.thingsboard.server.gen.transport.TransportProtos.SessionCloseNotificationProto;
import org.thingsboard.server.gen.transport.TransportProtos.SessionEvent;
import org.thingsboard.server.gen.transport.TransportProtos.SessionEventMsg;
import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto;
import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToAttributeUpdatesMsg;
import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToRPCMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg;
import org.thingsboard.server.gen.transport.TransportProtos.TsKvListProto;
import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto;
import org.thingsboard.server.service.rpc.FromDeviceRpcResponse;
import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg;
import org.thingsboard.server.service.rpc.ToServerRpcResponseActorMsg;
import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper;
import javax.annotation.Nullable;
import java.util.ArrayList;
@ -87,24 +83,22 @@ import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
* @author Andrew Shvayka
*/
public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
private final TenantId tenantId;
private final DeviceId deviceId;
private final Map<SessionId, SessionInfo> sessions;
private final Map<SessionId, SessionInfo> attributeSubscriptions;
private final Map<SessionId, SessionInfo> rpcSubscriptions;
@Slf4j
class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
final TenantId tenantId;
final DeviceId deviceId;
private final Map<UUID, SessionInfoMetaData> sessions;
private final Map<UUID, SessionInfo> attributeSubscriptions;
private final Map<UUID, SessionInfo> rpcSubscriptions;
private final Map<Integer, ToDeviceRpcRequestMetadata> toDeviceRpcPendingMap;
private final Map<Integer, ToServerRpcRequestMetadata> toServerRpcPendingMap;
private final Map<UUID, PendingSessionMsgData> pendingMsgs;
private final Gson gson = new Gson();
private final JsonParser jsonParser = new JsonParser();
@ -114,8 +108,8 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
private String deviceType;
private TbMsgMetaData defaultMetaData;
DeviceActorMessageProcessor(ActorSystemContext systemContext, LoggingAdapter logger, TenantId tenantId, DeviceId deviceId) {
super(systemContext, logger);
DeviceActorMessageProcessor(ActorSystemContext systemContext, TenantId tenantId, DeviceId deviceId) {
super(systemContext);
this.tenantId = tenantId;
this.deviceId = deviceId;
this.sessions = new LinkedHashMap<>();
@ -123,8 +117,8 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
this.rpcSubscriptions = new HashMap<>();
this.toDeviceRpcPendingMap = new HashMap<>();
this.toServerRpcPendingMap = new HashMap<>();
this.pendingMsgs = new HashMap<>();
initAttributes();
restoreSessions();
}
private void initAttributes() {
@ -139,41 +133,36 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
void processRpcRequest(ActorContext context, ToDeviceRpcRequestActorMsg msg) {
ToDeviceRpcRequest request = msg.getMsg();
ToDeviceRpcRequestBody body = request.getBody();
ToDeviceRpcRequestMsg rpcRequest = new ToDeviceRpcRequestMsg(
rpcSeq++,
body.getMethod(),
body.getParams()
);
ToDeviceRpcRequestMsg rpcRequest = ToDeviceRpcRequestMsg.newBuilder().setRequestId(
rpcSeq++).setMethodName(body.getMethod()).setParams(body.getParams()).build();
long timeout = request.getExpirationTime() - System.currentTimeMillis();
if (timeout <= 0) {
logger.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());
return;
}
boolean sent = rpcSubscriptions.size() > 0;
Set<SessionId> syncSessionSet = new HashSet<>();
rpcSubscriptions.entrySet().forEach(sub -> {
ActorSystemToDeviceSessionActorMsg response = new BasicActorSystemToDeviceSessionActorMsg(rpcRequest, sub.getKey());
sendMsgToSessionActor(response, sub.getValue().getServer());
if (SessionType.SYNC == sub.getValue().getType()) {
syncSessionSet.add(sub.getKey());
Set<UUID> syncSessionSet = new HashSet<>();
rpcSubscriptions.forEach((key, value) -> {
sendToTransport(rpcRequest, key, value.getNodeId());
if (TransportProtos.SessionType.SYNC == value.getType()) {
syncSessionSet.add(key);
}
});
syncSessionSet.forEach(rpcSubscriptions::remove);
if (request.isOneway() && sent) {
logger.debug("[{}] Rpc command response sent [{}]!", deviceId, request.getId());
systemContext.getDeviceRpcService().processRpcResponseFromDevice(new FromDeviceRpcResponse(msg.getMsg().getId(), msg.getServerAddress(), null, null));
log.debug("[{}] Rpc command response sent [{}]!", deviceId, request.getId());
systemContext.getDeviceRpcService().processResponseToServerSideRPCRequestFromDeviceActor(new FromDeviceRpcResponse(msg.getMsg().getId(), null, null));
} else {
registerPendingRpcRequest(context, msg, sent, rpcRequest, timeout);
}
if (sent) {
logger.debug("[{}] RPC request {} is sent!", deviceId, request.getId());
log.debug("[{}] RPC request {} is sent!", deviceId, request.getId());
} else {
logger.debug("[{}] RPC request {} is NOT sent!", deviceId, request.getId());
log.debug("[{}] RPC request {} is NOT sent!", deviceId, request.getId());
}
}
private void registerPendingRpcRequest(ActorContext context, ToDeviceRpcRequestActorMsg msg, boolean sent, ToDeviceRpcRequestMsg rpcRequest, long timeout) {
@ -185,101 +174,82 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
void processServerSideRpcTimeout(ActorContext context, DeviceActorServerSideRpcTimeoutMsg msg) {
ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(msg.getId());
if (requestMd != null) {
logger.debug("[{}] RPC request [{}] timeout detected!", deviceId, msg.getId());
systemContext.getDeviceRpcService().processRpcResponseFromDevice(new FromDeviceRpcResponse(requestMd.getMsg().getMsg().getId(),
requestMd.getMsg().getServerAddress(), null, requestMd.isSent() ? RpcError.TIMEOUT : RpcError.NO_ACTIVE_CONNECTION));
}
}
void processQueueTimeout(ActorContext context, DeviceActorQueueTimeoutMsg msg) {
PendingSessionMsgData data = pendingMsgs.remove(msg.getId());
if (data != null) {
logger.debug("[{}] Queue put [{}] timeout detected!", deviceId, msg.getId());
ToDeviceMsg toDeviceMsg = new RuleEngineErrorMsg(data.getSessionMsgType(), RuleEngineError.QUEUE_PUT_TIMEOUT);
sendMsgToSessionActor(new BasicActorSystemToDeviceSessionActorMsg(toDeviceMsg, data.getSessionId()), data.getServerAddress());
}
}
void processQueueAck(ActorContext context, RuleEngineQueuePutAckMsg msg) {
PendingSessionMsgData data = pendingMsgs.remove(msg.getId());
if (data != null && data.isReplyOnQueueAck()) {
int remainingAcks = data.getAckMsgCount() - 1;
data.setAckMsgCount(remainingAcks);
logger.debug("[{}] Queue put [{}] ack detected. Remaining acks: {}!", deviceId, msg.getId(), remainingAcks);
if (remainingAcks == 0) {
ToDeviceMsg toDeviceMsg = BasicStatusCodeResponse.onSuccess(data.getSessionMsgType(), data.getRequestId());
sendMsgToSessionActor(new BasicActorSystemToDeviceSessionActorMsg(toDeviceMsg, data.getSessionId()), data.getServerAddress());
}
log.debug("[{}] RPC request [{}] timeout detected!", deviceId, msg.getId());
systemContext.getDeviceRpcService().processResponseToServerSideRPCRequestFromDeviceActor(new FromDeviceRpcResponse(requestMd.getMsg().getMsg().getId(),
null, requestMd.isSent() ? RpcError.TIMEOUT : RpcError.NO_ACTIVE_CONNECTION));
}
}
private void sendPendingRequests(ActorContext context, SessionId sessionId, SessionType type, Optional<ServerAddress> server) {
private void sendPendingRequests(ActorContext context, UUID sessionId, SessionInfoProto sessionInfo) {
TransportProtos.SessionType sessionType = getSessionType(sessionId);
if (!toDeviceRpcPendingMap.isEmpty()) {
logger.debug("[{}] Pushing {} pending RPC messages to new async session [{}]", deviceId, toDeviceRpcPendingMap.size(), sessionId);
if (type == SessionType.SYNC) {
logger.debug("[{}] Cleanup sync rpc session [{}]", deviceId, sessionId);
log.debug("[{}] Pushing {} pending RPC messages to new async session [{}]", deviceId, toDeviceRpcPendingMap.size(), sessionId);
if (sessionType == TransportProtos.SessionType.SYNC) {
log.debug("[{}] Cleanup sync rpc session [{}]", deviceId, sessionId);
rpcSubscriptions.remove(sessionId);
}
} else {
logger.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<>();
if (type == SessionType.ASYNC) {
toDeviceRpcPendingMap.entrySet().forEach(processPendingRpc(context, sessionId, server, sentOneWayIds));
if (sessionType == TransportProtos.SessionType.ASYNC) {
toDeviceRpcPendingMap.entrySet().forEach(processPendingRpc(context, sessionId, sessionInfo.getNodeId(), sentOneWayIds));
} else {
toDeviceRpcPendingMap.entrySet().stream().findFirst().ifPresent(processPendingRpc(context, sessionId, server, sentOneWayIds));
toDeviceRpcPendingMap.entrySet().stream().findFirst().ifPresent(processPendingRpc(context, sessionId, sessionInfo.getNodeId(), sentOneWayIds));
}
sentOneWayIds.forEach(toDeviceRpcPendingMap::remove);
}
private Consumer<Map.Entry<Integer, ToDeviceRpcRequestMetadata>> processPendingRpc(ActorContext context, SessionId sessionId, Optional<ServerAddress> server, Set<Integer> sentOneWayIds) {
private Consumer<Map.Entry<Integer, ToDeviceRpcRequestMetadata>> processPendingRpc(ActorContext context, UUID sessionId, String nodeId, Set<Integer> sentOneWayIds) {
return entry -> {
ToDeviceRpcRequestActorMsg requestActorMsg = entry.getValue().getMsg();
ToDeviceRpcRequest request = entry.getValue().getMsg().getMsg();
ToDeviceRpcRequestBody body = request.getBody();
if (request.isOneway()) {
sentOneWayIds.add(entry.getKey());
systemContext.getDeviceRpcService().processRpcResponseFromDevice(new FromDeviceRpcResponse(request.getId(), requestActorMsg.getServerAddress(), null, null));
systemContext.getDeviceRpcService().processResponseToServerSideRPCRequestFromDeviceActor(new FromDeviceRpcResponse(request.getId(), null, null));
}
ToDeviceRpcRequestMsg rpcRequest = new ToDeviceRpcRequestMsg(
entry.getKey(),
body.getMethod(),
body.getParams()
);
ActorSystemToDeviceSessionActorMsg response = new BasicActorSystemToDeviceSessionActorMsg(rpcRequest, sessionId);
sendMsgToSessionActor(response, server);
ToDeviceRpcRequestMsg rpcRequest = ToDeviceRpcRequestMsg.newBuilder().setRequestId(
entry.getKey()).setMethodName(body.getMethod()).setParams(body.getParams()).build();
sendToTransport(rpcRequest, sessionId, nodeId);
};
}
void process(ActorContext context, DeviceToDeviceActorMsg msg) {
processSubscriptionCommands(context, msg);
processRpcResponses(context, msg);
processSessionStateMsgs(msg);
SessionMsgType sessionMsgType = msg.getPayload().getMsgType();
if (sessionMsgType.requiresRulesProcessing()) {
switch (sessionMsgType) {
case GET_ATTRIBUTES_REQUEST:
handleGetAttributesRequest(msg);
break;
case POST_ATTRIBUTES_REQUEST:
handlePostAttributesRequest(context, msg);
reportActivity();
break;
case POST_TELEMETRY_REQUEST:
handlePostTelemetryRequest(context, msg);
reportActivity();
break;
case TO_SERVER_RPC_REQUEST:
handleClientSideRPCRequest(context, msg);
reportActivity();
break;
}
void process(ActorContext context, TransportToDeviceActorMsgWrapper wrapper) {
TransportToDeviceActorMsg msg = wrapper.getMsg();
if (msg.hasSessionEvent()) {
processSessionStateMsgs(msg.getSessionInfo(), msg.getSessionEvent());
}
if (msg.hasSubscribeToAttributes()) {
processSubscriptionCommands(context, msg.getSessionInfo(), msg.getSubscribeToAttributes());
}
if (msg.hasSubscribeToRPC()) {
processSubscriptionCommands(context, msg.getSessionInfo(), msg.getSubscribeToRPC());
}
if (msg.hasPostAttributes()) {
handlePostAttributesRequest(context, msg.getSessionInfo(), msg.getPostAttributes());
reportLogicalDeviceActivity();
}
if (msg.hasPostTelemetry()) {
handlePostTelemetryRequest(context, msg.getSessionInfo(), msg.getPostTelemetry());
reportLogicalDeviceActivity();
}
if (msg.hasGetAttributes()) {
handleGetAttributesRequest(context, msg.getSessionInfo(), msg.getGetAttributes());
}
if (msg.hasToDeviceRPCCallResponse()) {
processRpcResponses(context, msg.getSessionInfo(), msg.getToDeviceRPCCallResponse());
}
if (msg.hasToServerRPCCallRequest()) {
handleClientSideRPCRequest(context, msg.getSessionInfo(), msg.getToServerRPCCallRequest());
reportLogicalDeviceActivity();
}
if (msg.hasSubscriptionInfo()) {
handleSessionActivity(context, msg.getSessionInfo(), msg.getSubscriptionInfo());
}
}
private void reportActivity() {
private void reportLogicalDeviceActivity() {
systemContext.getDeviceStateService().onDeviceActivity(deviceId);
}
@ -291,27 +261,27 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
systemContext.getDeviceStateService().onDeviceDisconnect(deviceId);
}
private void handleGetAttributesRequest(DeviceToDeviceActorMsg src) {
GetAttributesRequest request = (GetAttributesRequest) src.getPayload();
ListenableFuture<List<AttributeKvEntry>> clientAttributesFuture = getAttributeKvEntries(deviceId, DataConstants.CLIENT_SCOPE, request.getClientAttributeNames());
ListenableFuture<List<AttributeKvEntry>> sharedAttributesFuture = getAttributeKvEntries(deviceId, DataConstants.SHARED_SCOPE, request.getSharedAttributeNames());
private void handleGetAttributesRequest(ActorContext context, SessionInfoProto sessionInfo, GetAttributeRequestMsg request) {
ListenableFuture<List<AttributeKvEntry>> clientAttributesFuture = getAttributeKvEntries(deviceId, DataConstants.CLIENT_SCOPE, toOptionalSet(request.getClientAttributeNamesList()));
ListenableFuture<List<AttributeKvEntry>> sharedAttributesFuture = getAttributeKvEntries(deviceId, DataConstants.SHARED_SCOPE, toOptionalSet(request.getSharedAttributeNamesList()));
int requestId = request.getRequestId();
Futures.addCallback(Futures.allAsList(Arrays.asList(clientAttributesFuture, sharedAttributesFuture)), new FutureCallback<List<List<AttributeKvEntry>>>() {
@Override
public void onSuccess(@Nullable List<List<AttributeKvEntry>> result) {
BasicGetAttributesResponse response = BasicGetAttributesResponse.onSuccess(request.getMsgType(),
request.getRequestId(), BasicAttributeKVMsg.from(result.get(0), result.get(1)));
sendMsgToSessionActor(new BasicActorSystemToDeviceSessionActorMsg(response, src.getSessionId()), src.getServerAddress());
GetAttributeResponseMsg responseMsg = GetAttributeResponseMsg.newBuilder()
.setRequestId(requestId)
.addAllClientAttributeList(toTsKvProtos(result.get(0)))
.addAllSharedAttributeList(toTsKvProtos(result.get(1)))
.build();
sendToTransport(responseMsg, sessionInfo);
}
@Override
public void onFailure(Throwable t) {
if (t instanceof Exception) {
ToDeviceMsg toDeviceMsg = BasicStatusCodeResponse.onError(SessionMsgType.GET_ATTRIBUTES_REQUEST, request.getRequestId(), (Exception) t);
sendMsgToSessionActor(new BasicActorSystemToDeviceSessionActorMsg(toDeviceMsg, src.getSessionId()), src.getServerAddress());
} else {
logger.error("[{}] Failed to process attributes request", deviceId, t);
}
GetAttributeResponseMsg responseMsg = GetAttributeResponseMsg.newBuilder()
.setError(t.getMessage())
.build();
sendToTransport(responseMsg, sessionInfo);
}
});
}
@ -328,220 +298,221 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
}
}
private void handlePostAttributesRequest(ActorContext context, DeviceToDeviceActorMsg src) {
AttributesUpdateRequest request = (AttributesUpdateRequest) src.getPayload();
JsonObject json = new JsonObject();
for (AttributeKvEntry kv : request.getAttributes()) {
kv.getBooleanValue().ifPresent(v -> json.addProperty(kv.getKey(), v));
kv.getLongValue().ifPresent(v -> json.addProperty(kv.getKey(), v));
kv.getDoubleValue().ifPresent(v -> json.addProperty(kv.getKey(), v));
kv.getStrValue().ifPresent(v -> json.addProperty(kv.getKey(), v));
}
TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), SessionMsgType.POST_ATTRIBUTES_REQUEST.name(), deviceId, defaultMetaData.copy(), TbMsgDataType.JSON, gson.toJson(json), null, null, 0L);
PendingSessionMsgData msgData = new PendingSessionMsgData(src.getSessionId(), src.getServerAddress(),
SessionMsgType.POST_ATTRIBUTES_REQUEST, request.getRequestId(), true, 1);
pushToRuleEngineWithTimeout(context, tbMsg, msgData);
private void handlePostAttributesRequest(ActorContext context, SessionInfoProto sessionInfo, PostAttributeMsg postAttributes) {
JsonObject json = getJsonObject(postAttributes.getKvList());
TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), SessionMsgType.POST_ATTRIBUTES_REQUEST.name(), deviceId, defaultMetaData.copy(),
TbMsgDataType.JSON, gson.toJson(json), null, null, 0L);
pushToRuleEngine(context, tbMsg);
}
private void handlePostTelemetryRequest(ActorContext context, DeviceToDeviceActorMsg src) {
TelemetryUploadRequest request = (TelemetryUploadRequest) src.getPayload();
Map<Long, List<KvEntry>> tsData = request.getData();
PendingSessionMsgData msgData = new PendingSessionMsgData(src.getSessionId(), src.getServerAddress(),
SessionMsgType.POST_TELEMETRY_REQUEST, request.getRequestId(), true, tsData.size());
for (Map.Entry<Long, List<KvEntry>> entry : tsData.entrySet()) {
JsonObject json = new JsonObject();
for (KvEntry kv : entry.getValue()) {
kv.getBooleanValue().ifPresent(v -> json.addProperty(kv.getKey(), v));
kv.getLongValue().ifPresent(v -> json.addProperty(kv.getKey(), v));
kv.getDoubleValue().ifPresent(v -> json.addProperty(kv.getKey(), v));
kv.getStrValue().ifPresent(v -> json.addProperty(kv.getKey(), v));
}
private void handlePostTelemetryRequest(ActorContext context, SessionInfoProto sessionInfo, PostTelemetryMsg postTelemetry) {
for (TsKvListProto tsKv : postTelemetry.getTsKvListList()) {
JsonObject json = getJsonObject(tsKv.getKvList());
TbMsgMetaData metaData = defaultMetaData.copy();
metaData.putValue("ts", entry.getKey() + "");
metaData.putValue("ts", tsKv.getTs() + "");
TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, metaData, TbMsgDataType.JSON, gson.toJson(json), null, null, 0L);
pushToRuleEngineWithTimeout(context, tbMsg, msgData);
pushToRuleEngine(context, tbMsg);
}
}
private void handleClientSideRPCRequest(ActorContext context, DeviceToDeviceActorMsg src) {
ToServerRpcRequestMsg request = (ToServerRpcRequestMsg) src.getPayload();
private void handleClientSideRPCRequest(ActorContext context, SessionInfoProto sessionInfo, TransportProtos.ToServerRpcRequestMsg request) {
UUID sessionId = getSessionId(sessionInfo);
JsonObject json = new JsonObject();
json.addProperty("method", request.getMethod());
json.addProperty("method", request.getMethodName());
json.add("params", jsonParser.parse(request.getParams()));
TbMsgMetaData requestMetaData = defaultMetaData.copy();
requestMetaData.putValue("requestId", Integer.toString(request.getRequestId()));
TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), SessionMsgType.TO_SERVER_RPC_REQUEST.name(), deviceId, requestMetaData, TbMsgDataType.JSON, gson.toJson(json), null, null, 0L);
PendingSessionMsgData msgData = new PendingSessionMsgData(src.getSessionId(), src.getServerAddress(), SessionMsgType.TO_SERVER_RPC_REQUEST, request.getRequestId(), false, 1);
pushToRuleEngineWithTimeout(context, tbMsg, msgData);
context.parent().tell(new DeviceActorToRuleEngineMsg(context.self(), tbMsg), context.self());
scheduleMsgWithDelay(context, new DeviceActorClientSideRpcTimeoutMsg(request.getRequestId(), systemContext.getClientSideRpcTimeout()), systemContext.getClientSideRpcTimeout());
toServerRpcPendingMap.put(request.getRequestId(), new ToServerRpcRequestMetadata(src.getSessionId(), src.getSessionType(), src.getServerAddress()));
toServerRpcPendingMap.put(request.getRequestId(), new ToServerRpcRequestMetadata(sessionId, getSessionType(sessionId), sessionInfo.getNodeId()));
}
private TransportProtos.SessionType getSessionType(UUID sessionId) {
return sessions.containsKey(sessionId) ? TransportProtos.SessionType.ASYNC : TransportProtos.SessionType.SYNC;
}
public void processClientSideRpcTimeout(ActorContext context, DeviceActorClientSideRpcTimeoutMsg msg) {
void processClientSideRpcTimeout(ActorContext context, DeviceActorClientSideRpcTimeoutMsg msg) {
ToServerRpcRequestMetadata data = toServerRpcPendingMap.remove(msg.getId());
if (data != null) {
logger.debug("[{}] Client side RPC request [{}] timeout detected!", deviceId, msg.getId());
ToDeviceMsg toDeviceMsg = new RuleEngineErrorMsg(SessionMsgType.TO_SERVER_RPC_REQUEST, RuleEngineError.TIMEOUT);
sendMsgToSessionActor(new BasicActorSystemToDeviceSessionActorMsg(toDeviceMsg, data.getSessionId()), data.getServer());
log.debug("[{}] Client side RPC request [{}] timeout detected!", deviceId, msg.getId());
sendToTransport(TransportProtos.ToServerRpcResponseMsg.newBuilder()
.setRequestId(msg.getId()).setError("timeout").build()
, data.getSessionId(), data.getNodeId());
}
}
void processToServerRPCResponse(ActorContext context, ToServerRpcResponseActorMsg msg) {
ToServerRpcRequestMetadata data = toServerRpcPendingMap.remove(msg.getMsg().getRequestId());
int requestId = msg.getMsg().getRequestId();
ToServerRpcRequestMetadata data = toServerRpcPendingMap.remove(requestId);
if (data != null) {
sendMsgToSessionActor(new BasicActorSystemToDeviceSessionActorMsg(msg.getMsg(), data.getSessionId()), data.getServer());
log.debug("[{}] Pushing reply to [{}][{}]!", deviceId, data.getNodeId(), data.getSessionId());
sendToTransport(TransportProtos.ToServerRpcResponseMsg.newBuilder()
.setRequestId(requestId).setPayload(msg.getMsg().getData()).build()
, data.getSessionId(), data.getNodeId());
} else {
log.debug("[{}][{}] Pending RPC request to server not found!", deviceId, requestId);
}
}
private void pushToRuleEngineWithTimeout(ActorContext context, TbMsg tbMsg, PendingSessionMsgData pendingMsgData) {
SessionMsgType sessionMsgType = pendingMsgData.getSessionMsgType();
int requestId = pendingMsgData.getRequestId();
if (systemContext.isQueuePersistenceEnabled()) {
pendingMsgs.put(tbMsg.getId(), pendingMsgData);
scheduleMsgWithDelay(context, new DeviceActorQueueTimeoutMsg(tbMsg.getId(), systemContext.getQueuePersistenceTimeout()), systemContext.getQueuePersistenceTimeout());
} else {
ActorSystemToDeviceSessionActorMsg response = new BasicActorSystemToDeviceSessionActorMsg(BasicStatusCodeResponse.onSuccess(sessionMsgType, requestId), pendingMsgData.getSessionId());
sendMsgToSessionActor(response, pendingMsgData.getServerAddress());
}
private void pushToRuleEngine(ActorContext context, TbMsg tbMsg) {
context.parent().tell(new DeviceActorToRuleEngineMsg(context.self(), tbMsg), context.self());
}
void processAttributesUpdate(ActorContext context, DeviceAttributesEventNotificationMsg msg) {
if (attributeSubscriptions.size() > 0) {
ToDeviceMsg notification = null;
boolean hasNotificationData = false;
AttributeUpdateNotificationMsg.Builder notification = AttributeUpdateNotificationMsg.newBuilder();
if (msg.isDeleted()) {
List<AttributeKey> sharedKeys = msg.getDeletedKeys().stream()
List<String> sharedKeys = msg.getDeletedKeys().stream()
.filter(key -> DataConstants.SHARED_SCOPE.equals(key.getScope()))
.map(AttributeKey::getAttributeKey)
.collect(Collectors.toList());
notification = new AttributesUpdateNotification(BasicAttributeKVMsg.fromDeleted(sharedKeys));
if (!sharedKeys.isEmpty()) {
notification.addAllSharedDeleted(sharedKeys);
hasNotificationData = true;
}
} else {
if (DataConstants.SHARED_SCOPE.equals(msg.getScope())) {
List<AttributeKvEntry> attributes = new ArrayList<>(msg.getValues());
if (attributes.size() > 0) {
notification = new AttributesUpdateNotification(BasicAttributeKVMsg.fromShared(attributes));
List<TsKvProto> sharedUpdated = msg.getValues().stream().map(this::toTsKvProto)
.collect(Collectors.toList());
if (!sharedUpdated.isEmpty()) {
notification.addAllSharedUpdated(sharedUpdated);
hasNotificationData = true;
}
} else {
logger.debug("[{}] No public server side attributes changed!", deviceId);
log.debug("[{}] No public server side attributes changed!", deviceId);
}
}
}
if (notification != null) {
ToDeviceMsg finalNotification = notification;
if (hasNotificationData) {
AttributeUpdateNotificationMsg finalNotification = notification.build();
attributeSubscriptions.entrySet().forEach(sub -> {
ActorSystemToDeviceSessionActorMsg response = new BasicActorSystemToDeviceSessionActorMsg(finalNotification, sub.getKey());
sendMsgToSessionActor(response, sub.getValue().getServer());
sendToTransport(finalNotification, sub.getKey(), sub.getValue().getNodeId());
});
}
} else {
logger.debug("[{}] No registered attributes subscriptions to process!", deviceId);
log.debug("[{}] No registered attributes subscriptions to process!", deviceId);
}
}
private void processRpcResponses(ActorContext context, DeviceToDeviceActorMsg msg) {
SessionId sessionId = msg.getSessionId();
FromDeviceMsg inMsg = msg.getPayload();
if (inMsg.getMsgType() == SessionMsgType.TO_DEVICE_RPC_RESPONSE) {
logger.debug("[{}] Processing rpc command response [{}]", deviceId, sessionId);
ToDeviceRpcResponseMsg responseMsg = (ToDeviceRpcResponseMsg) inMsg;
ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(responseMsg.getRequestId());
boolean success = requestMd != null;
if (success) {
systemContext.getDeviceRpcService().processRpcResponseFromDevice(new FromDeviceRpcResponse(requestMd.getMsg().getMsg().getId(),
requestMd.getMsg().getServerAddress(), responseMsg.getData(), null));
} else {
logger.debug("[{}] Rpc command response [{}] is stale!", deviceId, responseMsg.getRequestId());
}
if (msg.getSessionType() == SessionType.SYNC) {
BasicCommandAckResponse response = success
? BasicCommandAckResponse.onSuccess(SessionMsgType.TO_DEVICE_RPC_REQUEST, responseMsg.getRequestId())
: BasicCommandAckResponse.onError(SessionMsgType.TO_DEVICE_RPC_REQUEST, responseMsg.getRequestId(), new TimeoutException());
sendMsgToSessionActor(new BasicActorSystemToDeviceSessionActorMsg(response, msg.getSessionId()), msg.getServerAddress());
}
private void processRpcResponses(ActorContext context, SessionInfoProto sessionInfo, ToDeviceRpcResponseMsg responseMsg) {
UUID sessionId = getSessionId(sessionInfo);
log.debug("[{}] Processing rpc command response [{}]", deviceId, sessionId);
ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(responseMsg.getRequestId());
boolean success = requestMd != null;
if (success) {
systemContext.getDeviceRpcService().processResponseToServerSideRPCRequestFromDeviceActor(new FromDeviceRpcResponse(requestMd.getMsg().getMsg().getId(),
responseMsg.getPayload(), null));
} else {
log.debug("[{}] Rpc command response [{}] is stale!", deviceId, responseMsg.getRequestId());
}
}
void processClusterEventMsg(ClusterEventMsg msg) {
if (!msg.isAdded()) {
logger.debug("[{}] Clearing attributes/rpc subscription for server [{}]", deviceId, msg.getServerAddress());
Predicate<Map.Entry<SessionId, SessionInfo>> filter = e -> e.getValue().getServer()
.map(serverAddress -> serverAddress.equals(msg.getServerAddress())).orElse(false);
attributeSubscriptions.entrySet().removeIf(filter);
rpcSubscriptions.entrySet().removeIf(filter);
private void processSubscriptionCommands(ActorContext context, SessionInfoProto sessionInfo, SubscribeToAttributeUpdatesMsg subscribeCmd) {
UUID sessionId = getSessionId(sessionInfo);
if (subscribeCmd.getUnsubscribe()) {
log.debug("[{}] Canceling attributes subscription for session [{}]", deviceId, sessionId);
attributeSubscriptions.remove(sessionId);
} else {
SessionInfoMetaData sessionMD = sessions.get(sessionId);
if (sessionMD == null) {
sessionMD = new SessionInfoMetaData(new SessionInfo(TransportProtos.SessionType.SYNC, sessionInfo.getNodeId()));
}
sessionMD.setSubscribedToAttributes(true);
log.debug("[{}] Registering attributes subscription for session [{}]", deviceId, sessionId);
attributeSubscriptions.put(sessionId, sessionMD.getSessionInfo());
dumpSessions();
}
}
private void processSubscriptionCommands(ActorContext context, DeviceToDeviceActorMsg msg) {
SessionId sessionId = msg.getSessionId();
SessionType sessionType = msg.getSessionType();
FromDeviceMsg inMsg = msg.getPayload();
if (inMsg.getMsgType() == SessionMsgType.SUBSCRIBE_ATTRIBUTES_REQUEST) {
logger.debug("[{}] Registering attributes subscription for session [{}]", deviceId, sessionId);
attributeSubscriptions.put(sessionId, new SessionInfo(sessionType, msg.getServerAddress()));
} else if (inMsg.getMsgType() == SessionMsgType.UNSUBSCRIBE_ATTRIBUTES_REQUEST) {
logger.debug("[{}] Canceling attributes subscription for session [{}]", deviceId, sessionId);
attributeSubscriptions.remove(sessionId);
} else if (inMsg.getMsgType() == SessionMsgType.SUBSCRIBE_RPC_COMMANDS_REQUEST) {
logger.debug("[{}] Registering rpc subscription for session [{}][{}]", deviceId, sessionId, sessionType);
rpcSubscriptions.put(sessionId, new SessionInfo(sessionType, msg.getServerAddress()));
sendPendingRequests(context, sessionId, sessionType, msg.getServerAddress());
} else if (inMsg.getMsgType() == SessionMsgType.UNSUBSCRIBE_RPC_COMMANDS_REQUEST) {
logger.debug("[{}] Canceling rpc subscription for session [{}][{}]", deviceId, sessionId, sessionType);
private UUID getSessionId(SessionInfoProto sessionInfo) {
return new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB());
}
private void processSubscriptionCommands(ActorContext context, SessionInfoProto sessionInfo, SubscribeToRPCMsg subscribeCmd) {
UUID sessionId = getSessionId(sessionInfo);
if (subscribeCmd.getUnsubscribe()) {
log.debug("[{}] Canceling rpc subscription for session [{}]", deviceId, sessionId);
rpcSubscriptions.remove(sessionId);
} else {
SessionInfoMetaData sessionMD = sessions.get(sessionId);
if (sessionMD == null) {
sessionMD = new SessionInfoMetaData(new SessionInfo(TransportProtos.SessionType.SYNC, sessionInfo.getNodeId()));
}
sessionMD.setSubscribedToRPC(true);
log.debug("[{}] Registering rpc subscription for session [{}]", deviceId, sessionId);
rpcSubscriptions.put(sessionId, sessionMD.getSessionInfo());
sendPendingRequests(context, sessionId, sessionInfo);
dumpSessions();
}
}
private void processSessionStateMsgs(DeviceToDeviceActorMsg msg) {
SessionId sessionId = msg.getSessionId();
FromDeviceMsg inMsg = msg.getPayload();
if (inMsg instanceof SessionOpenMsg) {
logger.debug("[{}] Processing new session [{}]", deviceId, sessionId);
private void processSessionStateMsgs(SessionInfoProto sessionInfo, SessionEventMsg msg) {
UUID sessionId = getSessionId(sessionInfo);
if (msg.getEvent() == SessionEvent.OPEN) {
if (sessions.containsKey(sessionId)) {
log.debug("[{}] Received duplicate session open event [{}]", deviceId, sessionId);
return;
}
log.debug("[{}] Processing new session [{}]", deviceId, sessionId);
if (sessions.size() >= systemContext.getMaxConcurrentSessionsPerDevice()) {
SessionId sessionIdToRemove = sessions.keySet().stream().findFirst().orElse(null);
UUID sessionIdToRemove = sessions.keySet().stream().findFirst().orElse(null);
if (sessionIdToRemove != null) {
closeSession(sessionIdToRemove, sessions.remove(sessionIdToRemove));
notifyTransportAboutClosedSession(sessionIdToRemove, sessions.remove(sessionIdToRemove));
}
}
sessions.put(sessionId, new SessionInfo(SessionType.ASYNC, msg.getServerAddress()));
sessions.put(sessionId, new SessionInfoMetaData(new SessionInfo(TransportProtos.SessionType.ASYNC, sessionInfo.getNodeId())));
if (sessions.size() == 1) {
reportSessionOpen();
}
} else if (inMsg instanceof SessionCloseMsg) {
logger.debug("[{}] Canceling subscriptions for closed session [{}]", deviceId, sessionId);
dumpSessions();
} else if (msg.getEvent() == SessionEvent.CLOSED) {
log.debug("[{}] Canceling subscriptions for closed session [{}]", deviceId, sessionId);
sessions.remove(sessionId);
attributeSubscriptions.remove(sessionId);
rpcSubscriptions.remove(sessionId);
if (sessions.isEmpty()) {
reportSessionClose();
}
dumpSessions();
}
}
private void sendMsgToSessionActor(ActorSystemToDeviceSessionActorMsg response, Optional<ServerAddress> sessionAddress) {
if (sessionAddress.isPresent()) {
ServerAddress address = sessionAddress.get();
logger.debug("{} Forwarding msg: {}", address, response);
systemContext.getRpcService().tell(systemContext.getEncodingService()
.convertToProtoDataMessage(sessionAddress.get(), response));
} else {
systemContext.getSessionManagerActor().tell(response, ActorRef.noSender());
private void handleSessionActivity(ActorContext context, SessionInfoProto sessionInfo, TransportProtos.SubscriptionInfoProto subscriptionInfo) {
UUID sessionId = getSessionId(sessionInfo);
SessionInfoMetaData sessionMD = sessions.get(sessionId);
if (sessionMD != null) {
sessionMD.setLastActivityTime(subscriptionInfo.getLastActivityTime());
sessionMD.setSubscribedToAttributes(subscriptionInfo.getAttributeSubscription());
sessionMD.setSubscribedToRPC(subscriptionInfo.getRpcSubscription());
if (subscriptionInfo.getAttributeSubscription()) {
attributeSubscriptions.putIfAbsent(sessionId, sessionMD.getSessionInfo());
}
if (subscriptionInfo.getRpcSubscription()) {
rpcSubscriptions.putIfAbsent(sessionId, sessionMD.getSessionInfo());
}
}
dumpSessions();
}
void processCredentialsUpdate() {
sessions.forEach(this::closeSession);
sessions.forEach(this::notifyTransportAboutClosedSession);
attributeSubscriptions.clear();
rpcSubscriptions.clear();
dumpSessions();
}
private void closeSession(SessionId sessionId, SessionInfo sessionInfo) {
sendMsgToSessionActor(new BasicActorSystemToDeviceSessionActorMsg(new SessionCloseNotification(), sessionId), sessionInfo.getServer());
private void notifyTransportAboutClosedSession(UUID sessionId, SessionInfoMetaData sessionMd) {
DeviceActorToTransportMsg msg = DeviceActorToTransportMsg.newBuilder()
.setSessionIdMSB(sessionId.getMostSignificantBits())
.setSessionIdLSB(sessionId.getLeastSignificantBits())
.setSessionCloseNotification(SessionCloseNotificationProto.getDefaultInstance()).build();
systemContext.getRuleEngineTransportService().process(sessionMd.getSessionInfo().getNodeId(), msg);
}
void processNameOrTypeUpdate(DeviceNameOrTypeUpdateMsg msg) {
@ -552,4 +523,178 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
this.defaultMetaData.putValue("deviceType", deviceType);
}
private JsonObject getJsonObject(List<KeyValueProto> tsKv) {
JsonObject json = new JsonObject();
for (KeyValueProto kv : tsKv) {
switch (kv.getType()) {
case BOOLEAN_V:
json.addProperty(kv.getKey(), kv.getBoolV());
break;
case LONG_V:
json.addProperty(kv.getKey(), kv.getLongV());
break;
case DOUBLE_V:
json.addProperty(kv.getKey(), kv.getDoubleV());
break;
case STRING_V:
json.addProperty(kv.getKey(), kv.getStringV());
break;
}
}
return json;
}
private Optional<Set<String>> toOptionalSet(List<String> strings) {
if (strings == null || strings.isEmpty()) {
return Optional.empty();
} else {
return Optional.of(new HashSet<>(strings));
}
}
private void sendToTransport(GetAttributeResponseMsg responseMsg, SessionInfoProto sessionInfo) {
DeviceActorToTransportMsg msg = DeviceActorToTransportMsg.newBuilder()
.setSessionIdMSB(sessionInfo.getSessionIdMSB())
.setSessionIdLSB(sessionInfo.getSessionIdLSB())
.setGetAttributesResponse(responseMsg).build();
systemContext.getRuleEngineTransportService().process(sessionInfo.getNodeId(), msg);
}
private void sendToTransport(AttributeUpdateNotificationMsg notificationMsg, UUID sessionId, String nodeId) {
DeviceActorToTransportMsg msg = DeviceActorToTransportMsg.newBuilder()
.setSessionIdMSB(sessionId.getMostSignificantBits())
.setSessionIdLSB(sessionId.getLeastSignificantBits())
.setAttributeUpdateNotification(notificationMsg).build();
systemContext.getRuleEngineTransportService().process(nodeId, msg);
}
private void sendToTransport(ToDeviceRpcRequestMsg rpcMsg, UUID sessionId, String nodeId) {
DeviceActorToTransportMsg msg = DeviceActorToTransportMsg.newBuilder()
.setSessionIdMSB(sessionId.getMostSignificantBits())
.setSessionIdLSB(sessionId.getLeastSignificantBits())
.setToDeviceRequest(rpcMsg).build();
systemContext.getRuleEngineTransportService().process(nodeId, msg);
}
private void sendToTransport(TransportProtos.ToServerRpcResponseMsg rpcMsg, UUID sessionId, String nodeId) {
DeviceActorToTransportMsg msg = DeviceActorToTransportMsg.newBuilder()
.setSessionIdMSB(sessionId.getMostSignificantBits())
.setSessionIdLSB(sessionId.getLeastSignificantBits())
.setToServerResponse(rpcMsg).build();
systemContext.getRuleEngineTransportService().process(nodeId, msg);
}
private List<TsKvProto> toTsKvProtos(@Nullable List<AttributeKvEntry> result) {
List<TsKvProto> clientAttributes;
if (result == null || result.isEmpty()) {
clientAttributes = Collections.emptyList();
} else {
clientAttributes = new ArrayList<>(result.size());
for (AttributeKvEntry attrEntry : result) {
clientAttributes.add(toTsKvProto(attrEntry));
}
}
return clientAttributes;
}
private TsKvProto toTsKvProto(AttributeKvEntry attrEntry) {
return TsKvProto.newBuilder().setTs(attrEntry.getLastUpdateTs())
.setKv(toKeyValueProto(attrEntry)).build();
}
private KeyValueProto toKeyValueProto(KvEntry kvEntry) {
KeyValueProto.Builder builder = KeyValueProto.newBuilder();
builder.setKey(kvEntry.getKey());
switch (kvEntry.getDataType()) {
case BOOLEAN:
builder.setType(KeyValueType.BOOLEAN_V);
builder.setBoolV(kvEntry.getBooleanValue().get());
break;
case DOUBLE:
builder.setType(KeyValueType.DOUBLE_V);
builder.setDoubleV(kvEntry.getDoubleValue().get());
break;
case LONG:
builder.setType(KeyValueType.LONG_V);
builder.setLongV(kvEntry.getLongValue().get());
break;
case STRING:
builder.setType(KeyValueType.STRING_V);
builder.setStringV(kvEntry.getStrValue().get());
break;
}
return builder.build();
}
private void restoreSessions() {
log.debug("[{}] Restoring sessions from cache", deviceId);
TransportProtos.DeviceSessionsCacheEntry sessionsDump = systemContext.getDeviceSessionCacheService().get(deviceId);
if (sessionsDump.getSerializedSize() == 0) {
log.debug("[{}] No session information found", deviceId);
return;
}
for (TransportProtos.SessionSubscriptionInfoProto sessionSubscriptionInfoProto : sessionsDump.getSessionsList()) {
SessionInfoProto sessionInfoProto = sessionSubscriptionInfoProto.getSessionInfo();
UUID sessionId = getSessionId(sessionInfoProto);
SessionInfo sessionInfo = new SessionInfo(TransportProtos.SessionType.ASYNC, sessionInfoProto.getNodeId());
TransportProtos.SubscriptionInfoProto subInfo = sessionSubscriptionInfoProto.getSubscriptionInfo();
SessionInfoMetaData sessionMD = new SessionInfoMetaData(sessionInfo, subInfo.getLastActivityTime());
sessions.put(sessionId, sessionMD);
if (subInfo.getAttributeSubscription()) {
attributeSubscriptions.put(sessionId, sessionInfo);
sessionMD.setSubscribedToAttributes(true);
}
if (subInfo.getRpcSubscription()) {
rpcSubscriptions.put(sessionId, sessionInfo);
sessionMD.setSubscribedToRPC(true);
}
log.debug("[{}] Restored session: {}", deviceId, sessionMD);
}
log.debug("[{}] Restored sessions: {}, rpc subscriptions: {}, attribute subscriptions: {}", deviceId, sessions.size(), rpcSubscriptions.size(), attributeSubscriptions.size());
}
private void dumpSessions() {
log.debug("[{}] Dumping sessions: {}, rpc subscriptions: {}, attribute subscriptions: {} to cache", deviceId, sessions.size(), rpcSubscriptions.size(), attributeSubscriptions.size());
List<TransportProtos.SessionSubscriptionInfoProto> sessionsList = new ArrayList<>(sessions.size());
sessions.forEach((uuid, sessionMD) -> {
if (sessionMD.getSessionInfo().getType() == TransportProtos.SessionType.SYNC) {
return;
}
SessionInfo sessionInfo = sessionMD.getSessionInfo();
TransportProtos.SubscriptionInfoProto subscriptionInfoProto = TransportProtos.SubscriptionInfoProto.newBuilder()
.setLastActivityTime(sessionMD.getLastActivityTime())
.setAttributeSubscription(sessionMD.isSubscribedToAttributes())
.setRpcSubscription(sessionMD.isSubscribedToRPC()).build();
TransportProtos.SessionInfoProto sessionInfoProto = TransportProtos.SessionInfoProto.newBuilder()
.setSessionIdMSB(uuid.getMostSignificantBits())
.setSessionIdLSB(uuid.getLeastSignificantBits())
.setNodeId(sessionInfo.getNodeId()).build();
sessionsList.add(TransportProtos.SessionSubscriptionInfoProto.newBuilder()
.setSessionInfo(sessionInfoProto)
.setSubscriptionInfo(subscriptionInfoProto).build());
log.debug("[{}] Dumping session: {}", deviceId, sessionMD);
});
systemContext.getDeviceSessionCacheService()
.put(deviceId, TransportProtos.DeviceSessionsCacheEntry.newBuilder()
.addAllSessions(sessionsList).build());
}
void initSessionTimeout(ActorContext context) {
schedulePeriodicMsgWithDelay(context, SessionTimeoutCheckMsg.instance(), systemContext.getSessionInactivityTimeout(), systemContext.getSessionInactivityTimeout());
}
void checkSessionsTimeout() {
long expTime = System.currentTimeMillis() - systemContext.getSessionInactivityTimeout();
Map<UUID, SessionInfoMetaData> sessionsToRemove = sessions.entrySet().stream().filter(kv -> kv.getValue().getLastActivityTime() < expTime).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
sessionsToRemove.forEach((sessionId, sessionMD) -> {
sessions.remove(sessionId);
rpcSubscriptions.remove(sessionId);
attributeSubscriptions.remove(sessionId);
notifyTransportAboutClosedSession(sessionId, sessionMD);
});
if (!sessionsToRemove.isEmpty()) {
dumpSessions();
}
}
}

40
application/src/main/java/org/thingsboard/server/actors/device/PendingSessionMsgData.java

@ -1,40 +0,0 @@
/**
* Copyright © 2016-2018 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.actors.device;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.thingsboard.server.common.data.id.SessionId;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.session.SessionMsgType;
import java.util.Optional;
/**
* Created by ashvayka on 17.04.18.
*/
@Data
@AllArgsConstructor
public final class PendingSessionMsgData {
private final SessionId sessionId;
private final Optional<ServerAddress> serverAddress;
private final SessionMsgType sessionMsgType;
private final int requestId;
private final boolean replyOnQueueAck;
private int ackMsgCount;
}

8
application/src/main/java/org/thingsboard/server/actors/device/SessionInfo.java

@ -16,10 +16,7 @@
package org.thingsboard.server.actors.device;
import lombok.Data;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.session.SessionType;
import java.util.Optional;
import org.thingsboard.server.gen.transport.TransportProtos.SessionType;
/**
* @author Andrew Shvayka
@ -27,5 +24,6 @@ import java.util.Optional;
@Data
public class SessionInfo {
private final SessionType type;
private final Optional<ServerAddress> server;
private final String nodeId;
private long lastActivityTime;
}

26
common/message/src/main/java/org/thingsboard/server/common/msg/core/ToDeviceRpcRequestMsg.java → application/src/main/java/org/thingsboard/server/actors/device/SessionInfoMetaData.java

@ -13,29 +13,27 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.msg.core;
package org.thingsboard.server.actors.device;
import lombok.Data;
import org.thingsboard.server.common.msg.session.SessionMsgType;
import org.thingsboard.server.common.msg.session.SessionMsgType;
import org.thingsboard.server.common.msg.session.ToDeviceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.SessionType;
/**
* @author Andrew Shvayka
*/
@Data
public class ToDeviceRpcRequestMsg implements ToDeviceMsg {
class SessionInfoMetaData {
private final SessionInfo sessionInfo;
private long lastActivityTime;
private boolean subscribedToAttributes;
private boolean subscribedToRPC;
private final int requestId;
private final String method;
private final String params;
public SessionMsgType getSessionMsgType() {
return SessionMsgType.TO_DEVICE_RPC_REQUEST;
SessionInfoMetaData(SessionInfo sessionInfo) {
this(sessionInfo, System.currentTimeMillis());
}
@Override
public boolean isSuccess() {
return true;
SessionInfoMetaData(SessionInfo sessionInfo, long lastActivityTime) {
this.sessionInfo = sessionInfo;
this.lastActivityTime = lastActivityTime;
}
}

19
application/src/main/java/org/thingsboard/server/actors/device/RuleEngineQueuePutAckMsg.java → application/src/main/java/org/thingsboard/server/actors/device/SessionTimeoutCheckMsg.java

@ -15,22 +15,25 @@
*/
package org.thingsboard.server.actors.device;
import lombok.Data;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.TbActorMsg;
import java.util.UUID;
/**
* Created by ashvayka on 15.03.18.
* Created by ashvayka on 29.10.18.
*/
@Data
public final class RuleEngineQueuePutAckMsg implements TbActorMsg {
public class SessionTimeoutCheckMsg implements TbActorMsg {
private static final SessionTimeoutCheckMsg INSTANCE = new SessionTimeoutCheckMsg();
private final UUID id;
private SessionTimeoutCheckMsg() {
}
public static SessionTimeoutCheckMsg instance() {
return INSTANCE;
}
@Override
public MsgType getMsgType() {
return MsgType.RULE_ENGINE_QUEUE_PUT_ACK_MSG;
return MsgType.SESSION_TIMEOUT_MSG;
}
}

12
application/src/main/java/org/thingsboard/server/actors/device/ToServerRpcRequestMetadata.java

@ -16,18 +16,16 @@
package org.thingsboard.server.actors.device;
import lombok.Data;
import org.thingsboard.server.common.data.id.SessionId;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.session.SessionType;
import org.thingsboard.server.gen.transport.TransportProtos;
import java.util.Optional;
import java.util.UUID;
/**
* @author Andrew Shvayka
*/
@Data
public class ToServerRpcRequestMetadata {
private final SessionId sessionId;
private final SessionType type;
private final Optional<ServerAddress> server;
private final UUID sessionId;
private final TransportProtos.SessionType type;
private final String nodeId;
}

12
application/src/main/java/org/thingsboard/server/actors/rpc/BasicRpcSessionListener.java

@ -32,7 +32,7 @@ public class BasicRpcSessionListener implements GrpcSessionListener {
private final ActorRef manager;
private final ActorRef self;
public BasicRpcSessionListener(ActorService service, ActorRef manager, ActorRef self) {
BasicRpcSessionListener(ActorService service, ActorRef manager, ActorRef self) {
this.service = service;
this.manager = manager;
this.self = self;
@ -40,7 +40,7 @@ public class BasicRpcSessionListener implements GrpcSessionListener {
@Override
public void onConnected(GrpcSession session) {
log.info("{} session started -> {}", getType(session), session.getRemoteServer());
log.info("[{}][{}] session started", session.getRemoteServer(), getType(session));
if (!session.isClient()) {
manager.tell(new RpcSessionConnectedMsg(session.getRemoteServer(), session.getSessionId()), self);
}
@ -48,21 +48,19 @@ public class BasicRpcSessionListener implements GrpcSessionListener {
@Override
public void onDisconnected(GrpcSession session) {
log.info("{} session closed -> {}", getType(session), session.getRemoteServer());
log.info("[{}][{}] session closed", session.getRemoteServer(), getType(session));
manager.tell(new RpcSessionDisconnectedMsg(session.isClient(), session.getRemoteServer()), self);
}
@Override
public void onReceiveClusterGrpcMsg(GrpcSession session, ClusterAPIProtos.ClusterMessage clusterMessage) {
log.trace("{} Service [{}] received session actor msg {}", getType(session),
session.getRemoteServer(),
clusterMessage);
log.trace("Received session actor msg from [{}][{}]: {}", session.getRemoteServer(), getType(session), clusterMessage);
service.onReceivedMsg(session.getRemoteServer(), clusterMessage);
}
@Override
public void onError(GrpcSession session, Throwable t) {
log.warn("{} session got error -> {}", getType(session), session.getRemoteServer(), t);
log.warn("[{}][{}] session got error -> {}", session.getRemoteServer(), getType(session), t);
manager.tell(new RpcSessionClosedMsg(session.isClient(), session.getRemoteServer()), self);
session.close();
}

32
application/src/main/java/org/thingsboard/server/actors/rpc/RpcManagerActor.java

@ -19,6 +19,7 @@ import akka.actor.ActorRef;
import akka.actor.Props;
import akka.event.Logging;
import akka.event.LoggingAdapter;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.service.ContextAwareActor;
import org.thingsboard.server.actors.service.ContextBasedCreator;
@ -26,6 +27,7 @@ import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.cluster.ServerType;
import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
import org.thingsboard.server.service.cluster.discovery.ServerInstance;
@ -36,15 +38,13 @@ import java.util.*;
*/
public class RpcManagerActor extends ContextAwareActor {
private final LoggingAdapter log = Logging.getLogger(getContext().system(), this);
private final Map<ServerAddress, SessionActorInfo> sessionActors;
private final Map<ServerAddress, Queue<ClusterAPIProtos.ClusterMessage>> pendingMsgs;
private final ServerAddress instance;
public RpcManagerActor(ActorSystemContext systemContext) {
private RpcManagerActor(ActorSystemContext systemContext) {
super(systemContext);
this.sessionActors = new HashMap<>();
this.pendingMsgs = new HashMap<>();
@ -54,7 +54,6 @@ public class RpcManagerActor extends ContextAwareActor {
.filter(otherServer -> otherServer.getServerAddress().compareTo(instance) > 0)
.forEach(otherServer -> onCreateSessionRequest(
new RpcSessionCreateRequestMsg(UUID.randomUUID(), otherServer.getServerAddress(), null)));
}
@Override
@ -100,24 +99,23 @@ public class RpcManagerActor extends ContextAwareActor {
private void onMsg(ClusterAPIProtos.ClusterMessage msg) {
if (msg.hasServerAddress()) {
ServerAddress address = new ServerAddress(msg.getServerAddress().getHost(),
msg.getServerAddress().getPort());
ServerAddress address = new ServerAddress(msg.getServerAddress().getHost(), msg.getServerAddress().getPort(), ServerType.CORE);
SessionActorInfo session = sessionActors.get(address);
if (session != null) {
log.debug("{} Forwarding msg to session actor", address);
log.debug("{} Forwarding msg to session actor: {}", address, msg);
session.getActor().tell(msg, ActorRef.noSender());
} else {
log.debug("{} Storing msg to pending queue", address);
log.debug("{} Storing msg to pending queue: {}", address, msg);
Queue<ClusterAPIProtos.ClusterMessage> queue = pendingMsgs.get(address);
if (queue == null) {
queue = new LinkedList<>();
pendingMsgs.put(new ServerAddress(
msg.getServerAddress().getHost(), msg.getServerAddress().getPort()), queue);
msg.getServerAddress().getHost(), msg.getServerAddress().getPort(), ServerType.CORE), queue);
}
queue.add(msg);
}
} else {
logger.warning("Cluster msg doesn't have set Server Address [{}]", msg);
log.warn("Cluster msg doesn't have server address [{}]", msg);
}
}
@ -162,9 +160,9 @@ public class RpcManagerActor extends ContextAwareActor {
}
private void onSessionClose(boolean reconnect, ServerAddress remoteAddress) {
log.debug("[{}] session closed. Should reconnect: {}", remoteAddress, reconnect);
log.info("[{}] session closed. Should reconnect: {}", remoteAddress, reconnect);
SessionActorInfo sessionRef = sessionActors.get(remoteAddress);
if (context().sender() != null && context().sender().equals(sessionRef.actor)) {
if (sessionRef != null && context().sender() != null && context().sender().equals(sessionRef.actor)) {
sessionActors.remove(remoteAddress);
pendingMsgs.remove(remoteAddress);
if (reconnect) {
@ -182,18 +180,18 @@ public class RpcManagerActor extends ContextAwareActor {
private void register(ServerAddress remoteAddress, UUID uuid, ActorRef sender) {
sessionActors.put(remoteAddress, new SessionActorInfo(uuid, sender));
log.debug("[{}][{}] Registering session actor.", remoteAddress, uuid);
log.info("[{}][{}] Registering session actor.", remoteAddress, uuid);
Queue<ClusterAPIProtos.ClusterMessage> data = pendingMsgs.remove(remoteAddress);
if (data != null) {
log.debug("[{}][{}] Forwarding {} pending messages.", remoteAddress, uuid, data.size());
log.info("[{}][{}] Forwarding {} pending messages.", remoteAddress, uuid, data.size());
data.forEach(msg -> sender.tell(new RpcSessionTellMsg(msg), ActorRef.noSender()));
} else {
log.debug("[{}][{}] No pending messages to forward.", remoteAddress, uuid);
log.info("[{}][{}] No pending messages to forward.", remoteAddress, uuid);
}
}
private ActorRef createSessionActor(RpcSessionCreateRequestMsg msg) {
log.debug("[{}] Creating session actor.", msg.getMsgUid());
log.info("[{}] Creating session actor.", msg.getMsgUid());
ActorRef actor = context().actorOf(
Props.create(new RpcSessionActor.ActorCreator(systemContext, msg.getMsgUid())).withDispatcher(DefaultActorService.RPC_DISPATCHER_NAME));
actor.tell(msg, context().self());
@ -208,7 +206,7 @@ public class RpcManagerActor extends ContextAwareActor {
}
@Override
public RpcManagerActor create() throws Exception {
public RpcManagerActor create() {
return new RpcManagerActor(context);
}
}

5
application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionActor.java

@ -18,6 +18,7 @@ package org.thingsboard.server.actors.rpc;
import akka.event.Logging;
import akka.event.LoggingAdapter;
import io.grpc.Channel;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.stub.StreamObserver;
import org.thingsboard.server.actors.ActorSystemContext;
@ -88,8 +89,8 @@ public class RpcSessionActor extends ContextAwareActor {
systemContext.getRpcService().onSessionCreated(msg.getMsgUid(), session.getInputStream());
} else {
// Client session
Channel channel = ManagedChannelBuilder.forAddress(remoteServer.getHost(), remoteServer.getPort()).usePlaintext(true).build();
session = new GrpcSession(remoteServer, listener);
ManagedChannel channel = ManagedChannelBuilder.forAddress(remoteServer.getHost(), remoteServer.getPort()).usePlaintext().build();
session = new GrpcSession(remoteServer, listener, channel);
session.initInputStream();
ClusterRpcServiceGrpc.ClusterRpcServiceStub stub = ClusterRpcServiceGrpc.newStub(channel);

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

@ -17,6 +17,7 @@ package org.thingsboard.server.actors.ruleChain;
import akka.actor.ActorRef;
import com.datastax.driver.core.utils.UUIDs;
import org.springframework.util.StringUtils;
import org.thingsboard.rule.engine.api.ListeningExecutor;
import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.rule.engine.api.RuleEngineDeviceRpcRequest;
@ -35,12 +36,15 @@ import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.cluster.ServerType;
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.tenant.TenantService;
@ -154,7 +158,7 @@ class DefaultTbContext implements TbContext {
@Override
public ScriptEngine createJsScriptEngine(String script, String... argNames) {
return new RuleNodeJsScriptEngine(mainCtx.getJsSandbox(), script, argNames);
return new RuleNodeJsScriptEngine(mainCtx.getJsSandbox(), nodeCtx.getSelf().getId(), script, argNames);
}
@Override
@ -212,6 +216,11 @@ class DefaultTbContext implements TbContext {
return mainCtx.getRelationService();
}
@Override
public EntityViewService getEntityViewService() {
return mainCtx.getEntityViewService();
}
@Override
public MailService getMailService() {
if (mainCtx.isAllowSystemMailService()) {
@ -226,16 +235,22 @@ class DefaultTbContext implements TbContext {
return new RuleEngineRpcService() {
@Override
public void sendRpcReply(DeviceId deviceId, int requestId, String body) {
mainCtx.getDeviceRpcService().sendRpcReplyToDevice(nodeCtx.getTenantId(), deviceId, requestId, body);
mainCtx.getDeviceRpcService().sendReplyToRpcCallFromDevice(nodeCtx.getTenantId(), deviceId, requestId, body);
}
@Override
public void sendRpcRequest(RuleEngineDeviceRpcRequest src, Consumer<RuleEngineDeviceRpcResponse> consumer) {
ToDeviceRpcRequest request = new ToDeviceRpcRequest(src.getRequestUUID(), nodeCtx.getTenantId(), src.getDeviceId(),
src.isOneway(), src.getExpirationTime(), new ToDeviceRpcRequestBody(src.getMethod(), src.getBody()));
mainCtx.getDeviceRpcService().processRpcRequestToDevice(request, response -> {
mainCtx.getDeviceRpcService().forwardServerSideRPCRequestToDeviceActor(request, response -> {
if (src.isRestApiCall()) {
mainCtx.getDeviceRpcService().processRestAPIRpcResponseFromRuleEngine(response);
ServerAddress requestOriginAddress;
if (!StringUtils.isEmpty(src.getOriginHost())) {
requestOriginAddress = new ServerAddress(src.getOriginHost(), src.getOriginPort(), ServerType.CORE);
} else {
requestOriginAddress = mainCtx.getRoutingService().getCurrentServer();
}
mainCtx.getDeviceRpcService().processResponseToServerSideRPCRequestFromRuleEngine(requestOriginAddress, response);
}
consumer.accept(RuleEngineDeviceRpcResponse.builder()
.deviceId(src.getDeviceId())

5
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActor.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.actors.ruleChain;
import akka.actor.ActorInitializationException;
import akka.actor.OneForOneStrategy;
import akka.actor.SupervisorStrategy;
import org.thingsboard.server.actors.ActorSystemContext;
@ -33,7 +34,7 @@ public class RuleChainActor extends ComponentActor<RuleChainId, RuleChainActorMe
private RuleChainActor(ActorSystemContext systemContext, TenantId tenantId, RuleChainId ruleChainId) {
super(systemContext, tenantId, ruleChainId);
setProcessor(new RuleChainActorMessageProcessor(tenantId, ruleChainId, systemContext,
logger, context().parent(), context().self()));
context().parent(), context().self()));
}
@Override
@ -79,7 +80,7 @@ public class RuleChainActor extends ComponentActor<RuleChainId, RuleChainActorMe
}
@Override
public RuleChainActor create() throws Exception {
public RuleChainActor create() {
return new RuleChainActor(context, tenantId, ruleChainId);
}
}

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

@ -23,9 +23,9 @@ import com.datastax.driver.core.utils.UUIDs;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.device.DeviceActorToRuleEngineMsg;
import org.thingsboard.server.actors.device.RuleEngineQueuePutAckMsg;
import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.actors.shared.ComponentMsgProcessor;
import org.thingsboard.server.common.data.EntityType;
@ -56,6 +56,7 @@ import java.util.stream.Collectors;
/**
* @author Andrew Shvayka
*/
@Slf4j
public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleChainId> {
private static final long DEFAULT_CLUSTER_PARTITION = 0L;
@ -68,59 +69,58 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
private RuleNodeId firstId;
private RuleNodeCtx firstNode;
private boolean started;
private String ruleChainName;
RuleChainActorMessageProcessor(TenantId tenantId, RuleChainId ruleChainId, ActorSystemContext systemContext
, LoggingAdapter logger, ActorRef parent, ActorRef self) {
super(systemContext, logger, tenantId, ruleChainId);
, ActorRef parent, ActorRef self) {
super(systemContext, tenantId, ruleChainId);
this.parent = parent;
this.self = self;
this.nodeActors = new HashMap<>();
this.nodeRoutes = new HashMap<>();
this.service = systemContext.getRuleChainService();
this.ruleChainName = ruleChainId.toString();
}
@Override
public void start(ActorContext context) throws Exception {
public String getComponentName() {
return null;
}
@Override
public void start(ActorContext context) {
if (!started) {
RuleChain ruleChain = service.findRuleChainById(entityId);
ruleChainName = ruleChain.getName();
List<RuleNode> ruleNodeList = service.getRuleChainNodes(entityId);
log.trace("[{}][{}] Starting rule chain with {} nodes", tenantId, entityId, ruleNodeList.size());
// Creating and starting the actors;
for (RuleNode ruleNode : ruleNodeList) {
log.trace("[{}][{}] Creating rule node [{}]: {}", entityId, ruleNode.getId(), ruleNode.getName(), ruleNode);
ActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode);
nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode));
}
initRoutes(ruleChain, ruleNodeList);
reprocess(ruleNodeList);
started = true;
} else {
onUpdate(context);
}
}
private void reprocess(List<RuleNode> ruleNodeList) {
for (RuleNode ruleNode : ruleNodeList) {
for (TbMsg tbMsg : queue.findUnprocessed(tenantId, ruleNode.getId().getId(), systemContext.getQueuePartitionId())) {
pushMsgToNode(nodeActors.get(ruleNode.getId()), tbMsg, "");
}
}
if (firstNode != null) {
for (TbMsg tbMsg : queue.findUnprocessed(tenantId, entityId.getId(), systemContext.getQueuePartitionId())) {
pushMsgToNode(firstNode, tbMsg, "");
}
}
}
@Override
public void onUpdate(ActorContext context) throws Exception {
public void onUpdate(ActorContext context) {
RuleChain ruleChain = service.findRuleChainById(entityId);
ruleChainName = ruleChain.getName();
List<RuleNode> ruleNodeList = service.getRuleChainNodes(entityId);
log.trace("[{}][{}] Updating rule chain with {} nodes", tenantId, entityId, ruleNodeList.size());
for (RuleNode ruleNode : ruleNodeList) {
RuleNodeCtx existing = nodeActors.get(ruleNode.getId());
if (existing == null) {
log.trace("[{}][{}] Creating rule node [{}]: {}", entityId, ruleNode.getId(), ruleNode.getName(), ruleNode);
ActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode);
nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode));
} else {
log.trace("[{}][{}] Updating rule node [{}]: {}", entityId, ruleNode.getId(), ruleNode.getName(), ruleNode);
existing.setSelf(ruleNode);
existing.getSelfActor().tell(new ComponentLifecycleMsg(tenantId, existing.getSelf().getId(), ComponentLifecycleEvent.UPDATED), self);
}
@ -129,16 +129,17 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
Set<RuleNodeId> existingNodes = ruleNodeList.stream().map(RuleNode::getId).collect(Collectors.toSet());
List<RuleNodeId> removedRules = nodeActors.keySet().stream().filter(node -> !existingNodes.contains(node)).collect(Collectors.toList());
removedRules.forEach(ruleNodeId -> {
log.trace("[{}][{}] Removing rule node [{}]", tenantId, entityId, ruleNodeId);
RuleNodeCtx removed = nodeActors.remove(ruleNodeId);
removed.getSelfActor().tell(new ComponentLifecycleMsg(tenantId, removed.getSelf().getId(), ComponentLifecycleEvent.DELETED), self);
});
initRoutes(ruleChain, ruleNodeList);
reprocess(ruleNodeList);
}
@Override
public void stop(ActorContext context) throws Exception {
public void stop(ActorContext context) {
log.trace("[{}][{}] Stopping rule chain with {} nodes", tenantId, entityId, nodeActors.size());
nodeActors.values().stream().map(RuleNodeCtx::getSelfActor).forEach(context::stop);
nodeActors.clear();
nodeRoutes.clear();
@ -147,7 +148,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
}
@Override
public void onClusterEventMsg(ClusterEventMsg msg) throws Exception {
public void onClusterEventMsg(ClusterEventMsg msg) {
}
@ -164,10 +165,12 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
// Populating the routes map;
for (RuleNode ruleNode : ruleNodeList) {
List<EntityRelation> relations = service.getRuleNodeRelations(ruleNode.getId());
log.trace("[{}][{}][{}] Processing rule node relations [{}]", tenantId, entityId, ruleNode.getId(), relations.size());
if (relations.size() == 0) {
nodeRoutes.put(ruleNode.getId(), Collections.emptyList());
} else {
for (EntityRelation relation : relations) {
log.trace("[{}][{}][{}] Processing rule node relation [{}]", tenantId, entityId, ruleNode.getId(), relation.getTo());
if (relation.getTo().getEntityType() == EntityType.RULE_NODE) {
RuleNodeCtx ruleNodeCtx = nodeActors.get(new RuleNodeId(relation.getTo().getId()));
if (ruleNodeCtx == null) {
@ -181,24 +184,23 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
}
firstId = ruleChain.getFirstRuleNodeId();
firstNode = nodeActors.get(ruleChain.getFirstRuleNodeId());
firstNode = nodeActors.get(firstId);
state = ComponentLifecycleState.ACTIVE;
}
void onServiceToRuleEngineMsg(ServiceToRuleEngineMsg envelope) {
log.trace("[{}][{}] Processing message [{}]: {}", entityId, firstId, envelope.getTbMsg().getId(), envelope.getTbMsg());
checkActive();
if (firstNode != null) {
putToQueue(enrichWithRuleChainId(envelope.getTbMsg()), msg -> pushMsgToNode(firstNode, msg, ""));
log.trace("[{}][{}] Pushing message to first rule node", entityId, firstId);
pushMsgToNode(firstNode, enrichWithRuleChainId(envelope.getTbMsg()), "");
}
}
void onDeviceActorToRuleEngineMsg(DeviceActorToRuleEngineMsg envelope) {
checkActive();
if (firstNode != null) {
putToQueue(enrichWithRuleChainId(envelope.getTbMsg()), msg -> {
pushMsgToNode(firstNode, msg, "");
envelope.getCallbackRef().tell(new RuleEngineQueuePutAckMsg(msg.getId()), self);
});
pushMsgToNode(firstNode, enrichWithRuleChainId(envelope.getTbMsg()), "");
}
}
@ -206,15 +208,16 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
checkActive();
if (envelope.isEnqueue()) {
if (firstNode != null) {
putToQueue(enrichWithRuleChainId(envelope.getMsg()), msg -> pushMsgToNode(firstNode, msg, envelope.getFromRelationType()));
pushMsgToNode(firstNode, enrichWithRuleChainId(envelope.getMsg()), envelope.getFromRelationType());
}
} else {
if (firstNode != null) {
pushMsgToNode(firstNode, envelope.getMsg(), envelope.getFromRelationType());
} else {
TbMsg msg = envelope.getMsg();
EntityId ackId = msg.getRuleNodeId() != null ? msg.getRuleNodeId() : msg.getRuleChainId();
queue.ack(tenantId, envelope.getMsg(), ackId.getId(), msg.getClusterPartition());
// TODO: Ack this message in Kafka
// TbMsg msg = envelope.getMsg();
// EntityId ackId = msg.getRuleNodeId() != null ? msg.getRuleNodeId() : msg.getRuleChainId();
// queue.ack(tenantId, envelope.getMsg(), ackId.getId(), msg.getClusterPartition());
}
}
}
@ -234,7 +237,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
private void onRemoteTellNext(ServerAddress serverAddress, RuleNodeToRuleChainTellNextMsg envelope) {
TbMsg msg = envelope.getMsg();
logger.debug("Forwarding [{}] msg to remote server [{}] due to changed originator id: [{}]", msg.getId(), serverAddress, msg.getOriginator());
log.debug("Forwarding [{}] msg to remote server [{}] due to changed originator id: [{}]", msg.getId(), serverAddress, msg.getOriginator());
envelope = new RemoteToRuleChainTellNextMsg(envelope, tenantId, entityId);
systemContext.getRpcService().tell(systemContext.getEncodingService().convertToProtoDataMessage(serverAddress, envelope));
}
@ -248,16 +251,20 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
int relationsCount = relations.size();
EntityId ackId = msg.getRuleNodeId() != null ? msg.getRuleNodeId() : msg.getRuleChainId();
if (relationsCount == 0) {
log.trace("[{}][{}][{}] No outbound relations to process", tenantId, entityId, msg.getId());
if (ackId != null) {
queue.ack(tenantId, msg, ackId.getId(), msg.getClusterPartition());
// TODO: Ack this message in Kafka
// queue.ack(tenantId, msg, ackId.getId(), msg.getClusterPartition());
}
} else if (relationsCount == 1) {
for (RuleNodeRelation relation : relations) {
log.trace("[{}][{}][{}] Pushing message to single target: [{}]", tenantId, entityId, msg.getId(), relation.getOut());
pushToTarget(msg, relation.getOut(), relation.getType());
}
} else {
for (RuleNodeRelation relation : relations) {
EntityId target = relation.getOut();
log.trace("[{}][{}][{}] Pushing message to multiple targets: [{}]", tenantId, entityId, msg.getId(), relation.getOut());
switch (target.getEntityType()) {
case RULE_NODE:
enqueueAndForwardMsgCopyToNode(msg, target, relation.getType());
@ -269,7 +276,8 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
}
//TODO: Ideally this should happen in async way when all targets confirm that the copied messages are successfully written to corresponding target queues.
if (ackId != null) {
queue.ack(tenantId, msg, ackId.getId(), msg.getClusterPartition());
// TODO: Ack this message in Kafka
// queue.ack(tenantId, msg, ackId.getId(), msg.getClusterPartition());
}
}
}
@ -296,7 +304,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
RuleNodeId targetId = new RuleNodeId(target.getId());
RuleNodeCtx targetNodeCtx = nodeActors.get(targetId);
TbMsg copy = msg.copy(UUIDs.timeBased(), entityId, targetId, DEFAULT_CLUSTER_PARTITION);
putToQueue(copy, queuedMsg -> pushMsgToNode(targetNodeCtx, queuedMsg, fromRelationType));
pushMsgToNode(targetNodeCtx, copy, fromRelationType);
}
private void pushToTarget(TbMsg msg, EntityId target, String fromRelationType) {

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

@ -32,7 +32,7 @@ public class RuleNodeActor extends ComponentActor<RuleNodeId, RuleNodeActorMessa
super(systemContext, tenantId, ruleNodeId);
this.ruleChainId = ruleChainId;
setProcessor(new RuleNodeActorMessageProcessor(tenantId, ruleChainId, ruleNodeId, systemContext,
logger, context().parent(), context().self()));
context().parent(), context().self()));
}
@Override
@ -60,7 +60,9 @@ public class RuleNodeActor extends ComponentActor<RuleNodeId, RuleNodeActorMessa
}
private void onRuleNodeToSelfMsg(RuleNodeToSelfMsg msg) {
logger.debug("[{}] Going to process rule msg: {}", id, msg.getMsg());
if (log.isDebugEnabled()) {
log.debug("[{}][{}][{}] Going to process rule msg: {}", ruleChainId, id, processor.getComponentName(), msg.getMsg());
}
try {
processor.onRuleToSelfMsg(msg);
increaseMessagesProcessedCount();
@ -70,7 +72,9 @@ public class RuleNodeActor extends ComponentActor<RuleNodeId, RuleNodeActorMessa
}
private void onRuleChainToRuleNodeMsg(RuleChainToRuleNodeMsg msg) {
logger.debug("[{}] Going to process rule msg: {}", id, msg.getMsg());
if (log.isDebugEnabled()) {
log.debug("[{}][{}][{}] Going to process rule msg: {}", ruleChainId, id, processor.getComponentName(), msg.getMsg());
}
try {
processor.onRuleChainToRuleNodeMsg(msg);
increaseMessagesProcessedCount();

13
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java

@ -44,8 +44,8 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
private TbContext defaultCtx;
RuleNodeActorMessageProcessor(TenantId tenantId, RuleChainId ruleChainId, RuleNodeId ruleNodeId, ActorSystemContext systemContext
, LoggingAdapter logger, ActorRef parent, ActorRef self) {
super(systemContext, logger, tenantId, ruleNodeId);
, ActorRef parent, ActorRef self) {
super(systemContext, tenantId, ruleNodeId);
this.parent = parent;
this.self = self;
this.service = systemContext.getRuleChainService();
@ -75,7 +75,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
}
@Override
public void stop(ActorContext context) throws Exception {
public void stop(ActorContext context) {
if (tbNode != null) {
tbNode.destroy();
}
@ -83,7 +83,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
}
@Override
public void onClusterEventMsg(ClusterEventMsg msg) throws Exception {
public void onClusterEventMsg(ClusterEventMsg msg) {
}
@ -111,6 +111,11 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
}
}
@Override
public String getComponentName() {
return ruleNode.getName();
}
private TbNode initComponent(RuleNode ruleNode) throws Exception {
Class<?> componentClazz = Class.forName(ruleNode.getType());
TbNode tbNode = (TbNode) (componentClazz.newInstance());

1
application/src/main/java/org/thingsboard/server/actors/service/ActorService.java

@ -35,5 +35,4 @@ public interface ActorService extends SessionMsgProcessor, RpcMsgListener, Disco
void onDeviceNameOrTypeUpdate(TenantId tenantId, DeviceId deviceId, String deviceName, String deviceType);
void onMsg(ServiceToRuleEngineMsg serviceToRuleEngineMsg);
}

16
application/src/main/java/org/thingsboard/server/actors/service/ComponentActor.java

@ -18,6 +18,7 @@ package org.thingsboard.server.actors.service;
import akka.actor.ActorRef;
import akka.event.Logging;
import akka.event.LoggingAdapter;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.shared.ComponentMsgProcessor;
import org.thingsboard.server.actors.stats.StatsPersistMsg;
@ -32,8 +33,6 @@ import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
*/
public abstract class ComponentActor<T extends EntityId, P extends ComponentMsgProcessor<T>> extends ContextAwareActor {
protected final LoggingAdapter logger = Logging.getLogger(getContext().system(), this);
private long lastPersistedErrorTs = 0L;
protected final TenantId tenantId;
protected final T id;
@ -54,13 +53,14 @@ public abstract class ComponentActor<T extends EntityId, P extends ComponentMsgP
@Override
public void preStart() {
try {
log.debug("[{}][{}][{}] Starting processor.", tenantId, id, id.getEntityType());
processor.start(context());
logLifecycleEvent(ComponentLifecycleEvent.STARTED);
if (systemContext.isStatisticsEnabled()) {
scheduleStatsPersistTick();
}
} catch (Exception e) {
logger.warning("[{}][{}] Failed to start {} processor: {}", tenantId, id, id.getEntityType(), e);
log.warn("[{}][{}] Failed to start {} processor: {}", tenantId, id, id.getEntityType(), e);
logAndPersist("OnStart", e, true);
logLifecycleEvent(ComponentLifecycleEvent.STARTED, e);
}
@ -70,7 +70,7 @@ public abstract class ComponentActor<T extends EntityId, P extends ComponentMsgP
try {
processor.scheduleStatsPersistTick(context(), systemContext.getStatisticsPersistFrequency());
} catch (Exception e) {
logger.error("[{}][{}] Failed to schedule statistics store message. No statistics is going to be stored: {}", tenantId, id, e.getMessage());
log.error("[{}][{}] Failed to schedule statistics store message. No statistics is going to be stored: {}", tenantId, id, e.getMessage());
logAndPersist("onScheduleStatsPersistMsg", e);
}
}
@ -78,16 +78,18 @@ public abstract class ComponentActor<T extends EntityId, P extends ComponentMsgP
@Override
public void postStop() {
try {
log.debug("[{}][{}] Stopping processor.", tenantId, id, id.getEntityType());
processor.stop(context());
logLifecycleEvent(ComponentLifecycleEvent.STOPPED);
} catch (Exception e) {
logger.warning("[{}][{}] Failed to stop {} processor: {}", tenantId, id, id.getEntityType(), e.getMessage());
log.warn("[{}][{}] Failed to stop {} processor: {}", tenantId, id, id.getEntityType(), e.getMessage());
logAndPersist("OnStop", e, true);
logLifecycleEvent(ComponentLifecycleEvent.STOPPED, e);
}
}
protected void onComponentLifecycleMsg(ComponentLifecycleMsg msg) {
log.debug("[{}][{}][{}] onComponentLifecycleMsg: [{}]", tenantId, id, id.getEntityType(), msg.getEvent());
try {
switch (msg.getEvent()) {
case CREATED:
@ -148,9 +150,9 @@ public abstract class ComponentActor<T extends EntityId, P extends ComponentMsgP
private void logAndPersist(String method, Exception e, boolean critical) {
errorsOccurred++;
if (critical) {
logger.warning("[{}][{}] Failed to process {} msg: {}", id, tenantId, method, e);
log.warn("[{}][{}][{}] Failed to process {} msg: {}", id, tenantId, processor.getComponentName(), method, e);
} else {
logger.debug("[{}][{}] Failed to process {} msg: {}", id, tenantId, method, e);
log.debug("[{}][{}][{}] Failed to process {} msg: {}", id, tenantId, processor.getComponentName(), method, e);
}
long ts = System.currentTimeMillis();
if (ts - lastPersistedErrorTs > getErrorPersistFrequency()) {

22
application/src/main/java/org/thingsboard/server/actors/service/ContextAwareActor.java

@ -15,14 +15,17 @@
*/
package org.thingsboard.server.actors.service;
import akka.actor.Terminated;
import akka.actor.UntypedActor;
import akka.event.Logging;
import akka.event.LoggingAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.msg.TbActorMsg;
public abstract class ContextAwareActor extends UntypedActor {
protected final LoggingAdapter logger = Logging.getLogger(getContext().system(), this);
protected final Logger log = LoggerFactory.getLogger(getClass());
public static final int ENTITY_PACK_LIMIT = 1024;
@ -35,21 +38,26 @@ public abstract class ContextAwareActor extends UntypedActor {
@Override
public void onReceive(Object msg) throws Exception {
if (logger.isDebugEnabled()) {
logger.debug("Processing msg: {}", msg);
if (log.isDebugEnabled()) {
log.debug("Processing msg: {}", msg);
}
if (msg instanceof TbActorMsg) {
try {
if (!process((TbActorMsg) msg)) {
logger.warning("Unknown message: {}!", msg);
log.warn("Unknown message: {}!", msg);
}
} catch (Exception e) {
throw e;
}
} else if (msg instanceof Terminated) {
processTermination((Terminated) msg);
} else {
logger.warning("Unknown message: {}!", msg);
log.warn("Unknown message: {}!", msg);
}
}
protected void processTermination(Terminated msg) {
}
protected abstract boolean process(TbActorMsg msg);
}

34
application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java

@ -30,7 +30,6 @@ import org.thingsboard.server.actors.app.AppActor;
import org.thingsboard.server.actors.rpc.RpcBroadcastMsg;
import org.thingsboard.server.actors.rpc.RpcManagerActor;
import org.thingsboard.server.actors.rpc.RpcSessionCreateRequestMsg;
import org.thingsboard.server.actors.session.SessionManagerActor;
import org.thingsboard.server.actors.stats.StatsActor;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.id.DeviceId;
@ -38,13 +37,11 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.aware.SessionAwareMsg;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.cluster.SendToClusterMsg;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
import org.thingsboard.server.service.cluster.discovery.DiscoveryService;
import org.thingsboard.server.service.cluster.discovery.ServerInstance;
@ -68,10 +65,7 @@ public class DefaultActorService implements ActorService {
public static final String APP_DISPATCHER_NAME = "app-dispatcher";
public static final String CORE_DISPATCHER_NAME = "core-dispatcher";
public static final String SYSTEM_RULE_DISPATCHER_NAME = "system-rule-dispatcher";
public static final String SYSTEM_PLUGIN_DISPATCHER_NAME = "system-plugin-dispatcher";
public static final String TENANT_RULE_DISPATCHER_NAME = "rule-dispatcher";
public static final String TENANT_PLUGIN_DISPATCHER_NAME = "plugin-dispatcher";
public static final String SESSION_DISPATCHER_NAME = "session-dispatcher";
public static final String RPC_DISPATCHER_NAME = "rpc-dispatcher";
@Autowired
@ -90,8 +84,6 @@ public class DefaultActorService implements ActorService {
private ActorRef appActor;
private ActorRef sessionManagerActor;
private ActorRef rpcManagerActor;
@PostConstruct
@ -104,10 +96,6 @@ public class DefaultActorService implements ActorService {
appActor = system.actorOf(Props.create(new AppActor.ActorCreator(actorContext)).withDispatcher(APP_DISPATCHER_NAME), "appActor");
actorContext.setAppActor(appActor);
sessionManagerActor = system.actorOf(Props.create(new SessionManagerActor.ActorCreator(actorContext)).withDispatcher(CORE_DISPATCHER_NAME),
"sessionManagerActor");
actorContext.setSessionManagerActor(sessionManagerActor);
rpcManagerActor = system.actorOf(Props.create(new RpcManagerActor.ActorCreator(actorContext)).withDispatcher(CORE_DISPATCHER_NAME),
"rpcManagerActor");
@ -115,8 +103,6 @@ public class DefaultActorService implements ActorService {
actorContext.setStatsActor(statsActor);
rpcService.init(this);
discoveryService.addListener(this);
log.info("Actor system initialized.");
}
@ -136,12 +122,6 @@ public class DefaultActorService implements ActorService {
appActor.tell(msg, ActorRef.noSender());
}
@Override
public void process(SessionAwareMsg msg) {
log.debug("Processing session aware msg: {}", msg);
sessionManagerActor.tell(msg, ActorRef.noSender());
}
@Override
public void onServerAdded(ServerInstance server) {
log.trace("Processing onServerAdded msg: {}", server);
@ -178,11 +158,6 @@ public class DefaultActorService implements ActorService {
appActor.tell(new SendToClusterMsg(deviceId, msg), ActorRef.noSender());
}
@Override
public void onMsg(ServiceToRuleEngineMsg msg) {
appActor.tell(msg, ActorRef.noSender());
}
public void broadcast(ToAllNodesMsg msg) {
actorContext.getEncodingService().encode(msg);
rpcService.broadcast(new RpcBroadcastMsg(ClusterAPIProtos.ClusterMessage
@ -196,16 +171,15 @@ public class DefaultActorService implements ActorService {
private void broadcast(ClusterEventMsg msg) {
this.appActor.tell(msg, ActorRef.noSender());
this.sessionManagerActor.tell(msg, ActorRef.noSender());
this.rpcManagerActor.tell(msg, ActorRef.noSender());
}
@Override
public void onReceivedMsg(ServerAddress source, ClusterAPIProtos.ClusterMessage msg) {
ServerAddress serverAddress = new ServerAddress(source.getHost(), source.getPort());
log.info("Received msg [{}] from [{}]", msg.getMessageType().name(), serverAddress);
ServerAddress serverAddress = new ServerAddress(source.getHost(), source.getPort(), source.getServerType());
if (log.isDebugEnabled()) {
log.info("MSG: ", msg);
log.info("Received msg [{}] from [{}]", msg.getMessageType().name(), serverAddress);
log.info("MSG: {}", msg);
}
switch (msg.getMessageType()) {
case CLUSTER_ACTOR_MESSAGE:
@ -239,7 +213,7 @@ public class DefaultActorService implements ActorService {
actorContext.getTsSubService().onRemoteTsUpdate(serverAddress, msg.getPayload().toByteArray());
break;
case CLUSTER_RPC_FROM_DEVICE_RESPONSE_MESSAGE:
actorContext.getDeviceRpcService().processRemoteResponseFromDevice(serverAddress, msg.getPayload().toByteArray());
actorContext.getDeviceRpcService().processResponseToServerSideRPCRequestFromRemoteServer(serverAddress, msg.getPayload().toByteArray());
break;
case CLUSTER_DEVICE_STATE_SERVICE_MESSAGE:
actorContext.getDeviceStateService().onRemoteMsg(serverAddress, msg.getPayload().toByteArray());

156
application/src/main/java/org/thingsboard/server/actors/session/ASyncMsgProcessor.java

@ -1,156 +0,0 @@
/**
* Copyright © 2016-2018 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.actors.session;
import akka.actor.ActorContext;
import akka.event.LoggingAdapter;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.shared.SessionTimeoutMsg;
import org.thingsboard.server.common.data.id.SessionId;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.core.AttributesSubscribeMsg;
import org.thingsboard.server.common.msg.core.ResponseMsg;
import org.thingsboard.server.common.msg.core.RpcSubscribeMsg;
import org.thingsboard.server.common.msg.core.SessionCloseMsg;
import org.thingsboard.server.common.msg.core.SessionOpenMsg;
import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
import org.thingsboard.server.common.msg.session.BasicSessionActorToAdaptorMsg;
import org.thingsboard.server.common.msg.session.FromDeviceMsg;
import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg;
import org.thingsboard.server.common.msg.session.SessionMsgType;
import org.thingsboard.server.common.msg.session.SessionType;
import org.thingsboard.server.common.msg.session.ToDeviceMsg;
import org.thingsboard.server.common.msg.session.TransportToDeviceSessionActorMsg;
import org.thingsboard.server.common.msg.session.ex.SessionException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
class ASyncMsgProcessor extends AbstractSessionActorMsgProcessor {
private boolean firstMsg = true;
private Map<Integer, DeviceToDeviceActorMsg> pendingMap = new HashMap<>();
private Optional<ServerAddress> currentTargetServer;
private boolean subscribedToAttributeUpdates;
private boolean subscribedToRpcCommands;
public ASyncMsgProcessor(ActorSystemContext ctx, LoggingAdapter logger, SessionId sessionId) {
super(ctx, logger, sessionId);
}
@Override
protected void processToDeviceActorMsg(ActorContext ctx, TransportToDeviceSessionActorMsg msg) {
updateSessionCtx(msg, SessionType.ASYNC);
DeviceToDeviceActorMsg pendingMsg = toDeviceMsg(msg);
FromDeviceMsg fromDeviceMsg = pendingMsg.getPayload();
if (firstMsg) {
if (fromDeviceMsg.getMsgType() != SessionMsgType.SESSION_OPEN) {
toDeviceMsg(new SessionOpenMsg()).ifPresent(m -> forwardToAppActor(ctx, m));
}
firstMsg = false;
}
switch (fromDeviceMsg.getMsgType()) {
case POST_TELEMETRY_REQUEST:
case POST_ATTRIBUTES_REQUEST:
FromDeviceRequestMsg requestMsg = (FromDeviceRequestMsg) fromDeviceMsg;
if (requestMsg.getRequestId() >= 0) {
logger.debug("[{}] Pending request {} registered", requestMsg.getRequestId(), requestMsg.getMsgType());
//TODO: handle duplicates.
pendingMap.put(requestMsg.getRequestId(), pendingMsg);
}
break;
case SUBSCRIBE_ATTRIBUTES_REQUEST:
subscribedToAttributeUpdates = true;
break;
case UNSUBSCRIBE_ATTRIBUTES_REQUEST:
subscribedToAttributeUpdates = false;
break;
case SUBSCRIBE_RPC_COMMANDS_REQUEST:
subscribedToRpcCommands = true;
break;
case UNSUBSCRIBE_RPC_COMMANDS_REQUEST:
subscribedToRpcCommands = false;
break;
default:
break;
}
currentTargetServer = forwardToAppActor(ctx, pendingMsg);
}
@Override
public void processToDeviceMsg(ActorContext context, ToDeviceMsg msg) {
try {
if (msg.getSessionMsgType() != SessionMsgType.SESSION_CLOSE) {
switch (msg.getSessionMsgType()) {
case STATUS_CODE_RESPONSE:
case GET_ATTRIBUTES_RESPONSE:
ResponseMsg responseMsg = (ResponseMsg) msg;
if (responseMsg.getRequestId() >= 0) {
logger.debug("[{}] Pending request processed: {}", responseMsg.getRequestId(), responseMsg);
pendingMap.remove(responseMsg.getRequestId());
}
break;
default:
break;
}
sessionCtx.onMsg(new BasicSessionActorToAdaptorMsg(this.sessionCtx, msg));
} else {
sessionCtx.onMsg(org.thingsboard.server.common.msg.session.ctrl.SessionCloseMsg.onCredentialsRevoked(sessionCtx.getSessionId()));
}
} catch (SessionException e) {
logger.warning("Failed to push session response msg", e);
}
}
@Override
public void processTimeoutMsg(ActorContext context, SessionTimeoutMsg msg) {
// TODO Auto-generated method stub
}
@Override
protected void cleanupSession(ActorContext ctx) {
toDeviceMsg(new SessionCloseMsg()).ifPresent(m -> forwardToAppActor(ctx, m));
}
@Override
public void processClusterEvent(ActorContext context, ClusterEventMsg msg) {
if (pendingMap.size() > 0 || subscribedToAttributeUpdates || subscribedToRpcCommands) {
Optional<ServerAddress> newTargetServer = systemContext.getRoutingService().resolveById(getDeviceId());
if (!newTargetServer.equals(currentTargetServer)) {
firstMsg = true;
currentTargetServer = newTargetServer;
pendingMap.values().forEach(v -> {
forwardToAppActor(context, v, currentTargetServer);
if (currentTargetServer.isPresent()) {
logger.debug("[{}] Forwarded msg to new server: {}", sessionId, currentTargetServer.get());
} else {
logger.debug("[{}] Forwarded msg to local server.", sessionId);
}
});
if (subscribedToAttributeUpdates) {
toDeviceMsg(new AttributesSubscribeMsg()).ifPresent(m -> forwardToAppActor(context, m, currentTargetServer));
logger.debug("[{}] Forwarded attributes subscription.", sessionId);
}
if (subscribedToRpcCommands) {
toDeviceMsg(new RpcSubscribeMsg()).ifPresent(m -> forwardToAppActor(context, m, currentTargetServer));
logger.debug("[{}] Forwarded rpc commands subscription.", sessionId);
}
}
}
}
}

122
application/src/main/java/org/thingsboard/server/actors/session/AbstractSessionActorMsgProcessor.java

@ -1,122 +0,0 @@
/**
* Copyright © 2016-2018 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.actors.session;
import akka.actor.ActorContext;
import akka.actor.ActorRef;
import akka.event.LoggingAdapter;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor;
import org.thingsboard.server.actors.shared.SessionTimeoutMsg;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.SessionId;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.cluster.SendToClusterMsg;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.device.BasicDeviceToDeviceActorMsg;
import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
import org.thingsboard.server.common.msg.session.AdaptorToSessionActorMsg;
import org.thingsboard.server.common.msg.session.FromDeviceMsg;
import org.thingsboard.server.common.msg.session.SessionContext;
import org.thingsboard.server.common.msg.session.SessionCtrlMsg;
import org.thingsboard.server.common.msg.session.SessionType;
import org.thingsboard.server.common.msg.session.ToDeviceMsg;
import org.thingsboard.server.common.msg.session.TransportToDeviceSessionActorMsg;
import org.thingsboard.server.common.msg.session.ctrl.SessionCloseMsg;
import java.util.Optional;
abstract class AbstractSessionActorMsgProcessor extends AbstractContextAwareMsgProcessor {
protected final SessionId sessionId;
protected SessionContext sessionCtx;
protected DeviceToDeviceActorMsg deviceToDeviceActorMsgPrototype;
protected AbstractSessionActorMsgProcessor(ActorSystemContext ctx, LoggingAdapter logger, SessionId sessionId) {
super(ctx, logger);
this.sessionId = sessionId;
}
protected abstract void processToDeviceActorMsg(ActorContext ctx, TransportToDeviceSessionActorMsg msg);
protected abstract void processTimeoutMsg(ActorContext context, SessionTimeoutMsg msg);
protected abstract void processToDeviceMsg(ActorContext context, ToDeviceMsg msg);
public abstract void processClusterEvent(ActorContext context, ClusterEventMsg msg);
protected void processSessionCtrlMsg(ActorContext ctx, SessionCtrlMsg msg) {
if (msg instanceof SessionCloseMsg) {
cleanupSession(ctx);
terminateSession(ctx, sessionId);
}
}
protected void cleanupSession(ActorContext ctx) {
}
protected void updateSessionCtx(TransportToDeviceSessionActorMsg msg, SessionType type) {
sessionCtx = msg.getSessionMsg().getSessionContext();
deviceToDeviceActorMsgPrototype = new BasicDeviceToDeviceActorMsg(msg, type);
}
protected DeviceToDeviceActorMsg toDeviceMsg(TransportToDeviceSessionActorMsg msg) {
AdaptorToSessionActorMsg adaptorMsg = msg.getSessionMsg();
return new BasicDeviceToDeviceActorMsg(deviceToDeviceActorMsgPrototype, adaptorMsg.getMsg());
}
protected Optional<DeviceToDeviceActorMsg> toDeviceMsg(FromDeviceMsg msg) {
if (deviceToDeviceActorMsgPrototype != null) {
return Optional.of(new BasicDeviceToDeviceActorMsg(deviceToDeviceActorMsgPrototype, msg));
} else {
return Optional.empty();
}
}
protected Optional<ServerAddress> forwardToAppActor(ActorContext ctx, DeviceToDeviceActorMsg toForward) {
Optional<ServerAddress> address = systemContext.getRoutingService().resolveById(toForward.getDeviceId());
forwardToAppActor(ctx, toForward, address);
return address;
}
protected Optional<ServerAddress> forwardToAppActorIfAddressChanged(ActorContext ctx, DeviceToDeviceActorMsg toForward, Optional<ServerAddress> oldAddress) {
Optional<ServerAddress> newAddress = systemContext.getRoutingService().resolveById(toForward.getDeviceId());
if (!newAddress.equals(oldAddress)) {
getAppActor().tell(new SendToClusterMsg(toForward.getDeviceId(), toForward
.toOtherAddress(systemContext.getRoutingService().getCurrentServer())), ctx.self());
}
return newAddress;
}
protected void forwardToAppActor(ActorContext ctx, DeviceToDeviceActorMsg toForward, Optional<ServerAddress> address) {
if (address.isPresent()) {
systemContext.getRpcService().tell(systemContext.getEncodingService().convertToProtoDataMessage(address.get(),
toForward.toOtherAddress(systemContext.getRoutingService().getCurrentServer())));
} else {
getAppActor().tell(toForward, ctx.self());
}
}
public static void terminateSession(ActorContext ctx, SessionId sessionId) {
ctx.parent().tell(new SessionTerminationMsg(sessionId), ActorRef.noSender());
ctx.stop(ctx.self());
}
public DeviceId getDeviceId() {
return deviceToDeviceActorMsgPrototype.getDeviceId();
}
}

143
application/src/main/java/org/thingsboard/server/actors/session/SessionActor.java

@ -1,143 +0,0 @@
/**
* Copyright © 2016-2018 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.actors.session;
import akka.actor.OneForOneStrategy;
import akka.actor.SupervisorStrategy;
import akka.event.Logging;
import akka.event.LoggingAdapter;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.service.ContextAwareActor;
import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.actors.shared.SessionTimeoutMsg;
import org.thingsboard.server.common.data.id.SessionId;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.core.ActorSystemToDeviceSessionActorMsg;
import org.thingsboard.server.common.msg.session.SessionCtrlMsg;
import org.thingsboard.server.common.msg.session.SessionMsg;
import org.thingsboard.server.common.msg.session.SessionType;
import org.thingsboard.server.common.msg.session.TransportToDeviceSessionActorMsg;
import org.thingsboard.server.common.msg.session.ctrl.SessionCloseMsg;
import scala.concurrent.duration.Duration;
public class SessionActor extends ContextAwareActor {
private final LoggingAdapter logger = Logging.getLogger(getContext().system(), this);
private final SessionId sessionId;
private AbstractSessionActorMsgProcessor processor;
private SessionActor(ActorSystemContext systemContext, SessionId sessionId) {
super(systemContext);
this.sessionId = sessionId;
}
@Override
public SupervisorStrategy supervisorStrategy() {
return new OneForOneStrategy(-1, Duration.Inf(),
throwable -> {
logger.error(throwable, "Unknown session error");
if (throwable instanceof Error) {
return OneForOneStrategy.escalate();
} else {
return OneForOneStrategy.resume();
}
});
}
@Override
protected boolean process(TbActorMsg msg) {
switch (msg.getMsgType()) {
case TRANSPORT_TO_DEVICE_SESSION_ACTOR_MSG:
processTransportToSessionMsg((TransportToDeviceSessionActorMsg) msg);
break;
case ACTOR_SYSTEM_TO_DEVICE_SESSION_ACTOR_MSG:
processActorsToSessionMsg((ActorSystemToDeviceSessionActorMsg) msg);
break;
case SESSION_TIMEOUT_MSG:
processTimeoutMsg((SessionTimeoutMsg) msg);
break;
case SESSION_CTRL_MSG:
processSessionCloseMsg((SessionCtrlMsg) msg);
break;
case CLUSTER_EVENT_MSG:
processClusterEvent((ClusterEventMsg) msg);
break;
default: return false;
}
return true;
}
private void processClusterEvent(ClusterEventMsg msg) {
processor.processClusterEvent(context(), msg);
}
private void processTransportToSessionMsg(TransportToDeviceSessionActorMsg msg) {
initProcessor(msg);
processor.processToDeviceActorMsg(context(), msg);
}
private void processActorsToSessionMsg(ActorSystemToDeviceSessionActorMsg msg) {
processor.processToDeviceMsg(context(), msg.getMsg());
}
private void processTimeoutMsg(SessionTimeoutMsg msg) {
if (processor != null) {
processor.processTimeoutMsg(context(), msg);
} else {
logger.warning("[{}] Can't process timeout msg: {} without processor", sessionId, msg);
}
}
private void processSessionCloseMsg(SessionCtrlMsg msg) {
if (processor != null) {
processor.processSessionCtrlMsg(context(), msg);
} else if (msg instanceof SessionCloseMsg) {
AbstractSessionActorMsgProcessor.terminateSession(context(), sessionId);
} else {
logger.warning("[{}] Can't process session ctrl msg: {} without processor", sessionId, msg);
}
}
private void initProcessor(TransportToDeviceSessionActorMsg msg) {
if (processor == null) {
SessionMsg sessionMsg = (SessionMsg) msg.getSessionMsg();
if (sessionMsg.getSessionContext().getSessionType() == SessionType.SYNC) {
processor = new SyncMsgProcessor(systemContext, logger, sessionId);
} else {
processor = new ASyncMsgProcessor(systemContext, logger, sessionId);
}
}
}
public static class ActorCreator extends ContextBasedCreator<SessionActor> {
private static final long serialVersionUID = 1L;
private final SessionId sessionId;
public ActorCreator(ActorSystemContext context, SessionId sessionId) {
super(context);
this.sessionId = sessionId;
}
@Override
public SessionActor create() throws Exception {
return new SessionActor(context, sessionId);
}
}
}

180
application/src/main/java/org/thingsboard/server/actors/session/SessionManagerActor.java

@ -1,180 +0,0 @@
/**
* Copyright © 2016-2018 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.actors.session;
import akka.actor.ActorInitializationException;
import akka.actor.ActorRef;
import akka.actor.InvalidActorNameException;
import akka.actor.LocalActorRef;
import akka.actor.OneForOneStrategy;
import akka.actor.Props;
import akka.actor.SupervisorStrategy;
import akka.actor.Terminated;
import akka.event.Logging;
import akka.event.LoggingAdapter;
import akka.japi.Function;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.service.ContextAwareActor;
import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.actors.shared.SessionTimeoutMsg;
import org.thingsboard.server.common.data.id.SessionId;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.aware.SessionAwareMsg;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.core.ActorSystemToDeviceSessionActorMsg;
import org.thingsboard.server.common.msg.core.SessionCloseMsg;
import org.thingsboard.server.common.msg.session.SessionCtrlMsg;
import scala.concurrent.duration.Duration;
import java.util.HashMap;
import java.util.Map;
public class SessionManagerActor extends ContextAwareActor {
private static final int INITIAL_SESSION_MAP_SIZE = 1024;
private final LoggingAdapter log = Logging.getLogger(getContext().system(), this);
private final Map<String, ActorRef> sessionActors;
SessionManagerActor(ActorSystemContext systemContext) {
super(systemContext);
this.sessionActors = new HashMap<>(INITIAL_SESSION_MAP_SIZE);
}
@Override
public SupervisorStrategy supervisorStrategy() {
return strategy;
}
@Override
protected boolean process(TbActorMsg msg) {
//TODO Move everything here, to work with TbActorMsg
return false;
}
@Override
public void onReceive(Object msg) throws Exception {
if (msg instanceof SessionCtrlMsg) {
onSessionCtrlMsg((SessionCtrlMsg) msg);
} else if (msg instanceof SessionAwareMsg) {
forwardToSessionActor((SessionAwareMsg) msg);
} else if (msg instanceof SessionTerminationMsg) {
onSessionTermination((SessionTerminationMsg) msg);
} else if (msg instanceof Terminated) {
onTermination((Terminated) msg);
} else if (msg instanceof SessionTimeoutMsg) {
onSessionTimeout((SessionTimeoutMsg) msg);
} else if (msg instanceof ClusterEventMsg) {
broadcast(msg);
}
}
private void broadcast(Object msg) {
sessionActors.values().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender()));
}
private void onSessionTimeout(SessionTimeoutMsg msg) {
String sessionIdStr = msg.getSessionId().toUidStr();
ActorRef sessionActor = sessionActors.get(sessionIdStr);
if (sessionActor != null) {
sessionActor.tell(msg, ActorRef.noSender());
}
}
private void onSessionCtrlMsg(SessionCtrlMsg msg) {
String sessionIdStr = msg.getSessionId().toUidStr();
ActorRef sessionActor = sessionActors.get(sessionIdStr);
if (sessionActor != null) {
sessionActor.tell(msg, ActorRef.noSender());
}
}
private void onSessionTermination(SessionTerminationMsg msg) {
String sessionIdStr = msg.getId().toUidStr();
ActorRef sessionActor = sessionActors.remove(sessionIdStr);
if (sessionActor != null) {
log.debug("[{}] Removed session actor.", sessionIdStr);
//TODO: onSubscriptionUpdate device actor about session close;
} else {
log.debug("[{}] Session actor was already removed.", sessionIdStr);
}
}
private void forwardToSessionActor(SessionAwareMsg msg) {
if (msg instanceof ActorSystemToDeviceSessionActorMsg || msg instanceof SessionCloseMsg) {
String sessionIdStr = msg.getSessionId().toUidStr();
ActorRef sessionActor = sessionActors.get(sessionIdStr);
if (sessionActor != null) {
sessionActor.tell(msg, ActorRef.noSender());
} else {
log.debug("[{}] Session actor was already removed.", sessionIdStr);
}
} else {
try {
getOrCreateSessionActor(msg.getSessionId()).tell(msg, self());
} catch (InvalidActorNameException e) {
log.info("Invalid msg : {}", msg);
}
}
}
private ActorRef getOrCreateSessionActor(SessionId sessionId) {
String sessionIdStr = sessionId.toUidStr();
ActorRef sessionActor = sessionActors.get(sessionIdStr);
if (sessionActor == null) {
log.debug("[{}] Creating session actor.", sessionIdStr);
sessionActor = context().actorOf(
Props.create(new SessionActor.ActorCreator(systemContext, sessionId)).withDispatcher(DefaultActorService.SESSION_DISPATCHER_NAME),
sessionIdStr);
sessionActors.put(sessionIdStr, sessionActor);
log.debug("[{}] Created session actor.", sessionIdStr);
}
return sessionActor;
}
private void onTermination(Terminated message) {
ActorRef terminated = message.actor();
if (terminated instanceof LocalActorRef) {
log.info("Removed actor: {}.", terminated);
//TODO: cleanup session actors map
} else {
throw new IllegalStateException("Remote actors are not supported!");
}
}
public static class ActorCreator extends ContextBasedCreator<SessionManagerActor> {
private static final long serialVersionUID = 1L;
public ActorCreator(ActorSystemContext context) {
super(context);
}
@Override
public SessionManagerActor create() throws Exception {
return new SessionManagerActor(context);
}
}
private final SupervisorStrategy strategy = new OneForOneStrategy(3, Duration.create("1 minute"), new Function<Throwable, SupervisorStrategy.Directive>() {
@Override
public SupervisorStrategy.Directive apply(Throwable t) {
logger.error(t, "Unknown failure");
return SupervisorStrategy.stop();
}
});
}

95
application/src/main/java/org/thingsboard/server/actors/session/SyncMsgProcessor.java

@ -1,95 +0,0 @@
/**
* Copyright © 2016-2018 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.actors.session;
import akka.actor.ActorContext;
import akka.event.LoggingAdapter;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.shared.SessionTimeoutMsg;
import org.thingsboard.server.common.data.id.SessionId;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
import org.thingsboard.server.common.msg.session.BasicSessionActorToAdaptorMsg;
import org.thingsboard.server.common.msg.session.SessionContext;
import org.thingsboard.server.common.msg.session.SessionType;
import org.thingsboard.server.common.msg.session.ToDeviceMsg;
import org.thingsboard.server.common.msg.session.TransportToDeviceSessionActorMsg;
import org.thingsboard.server.common.msg.session.ctrl.SessionCloseMsg;
import org.thingsboard.server.common.msg.session.ex.SessionException;
import java.util.Optional;
class SyncMsgProcessor extends AbstractSessionActorMsgProcessor {
private DeviceToDeviceActorMsg pendingMsg;
private Optional<ServerAddress> currentTargetServer;
private boolean pendingResponse;
public SyncMsgProcessor(ActorSystemContext ctx, LoggingAdapter logger, SessionId sessionId) {
super(ctx, logger, sessionId);
}
@Override
protected void processToDeviceActorMsg(ActorContext ctx, TransportToDeviceSessionActorMsg msg) {
updateSessionCtx(msg, SessionType.SYNC);
pendingMsg = toDeviceMsg(msg);
pendingResponse = true;
currentTargetServer = forwardToAppActor(ctx, pendingMsg);
scheduleMsgWithDelay(ctx, new SessionTimeoutMsg(sessionId), getTimeout(systemContext, msg.getSessionMsg().getSessionContext()), ctx.parent());
}
public void processTimeoutMsg(ActorContext context, SessionTimeoutMsg msg) {
if (pendingResponse) {
try {
sessionCtx.onMsg(SessionCloseMsg.onTimeout(sessionId));
} catch (SessionException e) {
logger.warning("Failed to push session close msg", e);
}
terminateSession(context, this.sessionId);
}
}
public void processToDeviceMsg(ActorContext context, ToDeviceMsg msg) {
try {
sessionCtx.onMsg(new BasicSessionActorToAdaptorMsg(this.sessionCtx, msg));
pendingResponse = false;
} catch (SessionException e) {
logger.warning("Failed to push session response msg", e);
}
terminateSession(context, this.sessionId);
}
@Override
public void processClusterEvent(ActorContext context, ClusterEventMsg msg) {
if (pendingResponse) {
Optional<ServerAddress> newTargetServer = forwardToAppActorIfAddressChanged(context, pendingMsg, currentTargetServer);
if (logger.isDebugEnabled()) {
if (!newTargetServer.equals(currentTargetServer)) {
if (newTargetServer.isPresent()) {
logger.debug("[{}] Forwarded msg to new server: {}", sessionId, newTargetServer.get());
} else {
logger.debug("[{}] Forwarded msg to local server.", sessionId);
}
}
}
currentTargetServer = newTargetServer;
}
}
private long getTimeout(ActorSystemContext ctx, SessionContext sessionCtx) {
return sessionCtx.getTimeout() > 0 ? sessionCtx.getTimeout() : ctx.getSyncSessionTimeout();
}
}

34
application/src/main/java/org/thingsboard/server/actors/shared/AbstractContextAwareMsgProcessor.java

@ -22,61 +22,49 @@ import akka.event.LoggingAdapter;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.actors.ActorSystemContext;
import scala.concurrent.ExecutionContextExecutor;
import scala.concurrent.duration.Duration;
import java.util.concurrent.TimeUnit;
@Slf4j
public abstract class AbstractContextAwareMsgProcessor {
protected final ActorSystemContext systemContext;
protected final LoggingAdapter logger;
protected final ObjectMapper mapper = new ObjectMapper();
protected AbstractContextAwareMsgProcessor(ActorSystemContext systemContext, LoggingAdapter logger) {
protected AbstractContextAwareMsgProcessor(ActorSystemContext systemContext) {
super();
this.systemContext = systemContext;
this.logger = logger;
}
protected ActorRef getAppActor() {
return systemContext.getAppActor();
}
protected Scheduler getScheduler() {
private Scheduler getScheduler() {
return systemContext.getScheduler();
}
protected ExecutionContextExecutor getSystemDispatcher() {
private ExecutionContextExecutor getSystemDispatcher() {
return systemContext.getActorSystem().dispatcher();
}
protected void schedulePeriodicMsgWithDelay(ActorContext ctx, Object msg, long delayInMs, long periodInMs) {
schedulePeriodicMsgWithDelay(ctx, msg, delayInMs, periodInMs, ctx.self());
schedulePeriodicMsgWithDelay(msg, delayInMs, periodInMs, ctx.self());
}
protected void schedulePeriodicMsgWithDelay(ActorContext ctx, Object msg, long delayInMs, long periodInMs, ActorRef target) {
logger.debug("Scheduling periodic msg {} every {} ms with delay {} ms", msg, periodInMs, delayInMs);
private void schedulePeriodicMsgWithDelay(Object msg, long delayInMs, long periodInMs, ActorRef target) {
log.debug("Scheduling periodic msg {} every {} ms with delay {} ms", msg, periodInMs, delayInMs);
getScheduler().schedule(Duration.create(delayInMs, TimeUnit.MILLISECONDS), Duration.create(periodInMs, TimeUnit.MILLISECONDS), target, msg, getSystemDispatcher(), null);
}
protected void scheduleMsgWithDelay(ActorContext ctx, Object msg, long delayInMs) {
scheduleMsgWithDelay(ctx, msg, delayInMs, ctx.self());
scheduleMsgWithDelay(msg, delayInMs, ctx.self());
}
protected void scheduleMsgWithDelay(ActorContext ctx, Object msg, long delayInMs, ActorRef target) {
logger.debug("Scheduling msg {} with delay {} ms", msg, delayInMs);
private void scheduleMsgWithDelay(Object msg, long delayInMs, ActorRef target) {
log.debug("Scheduling msg {} with delay {} ms", msg, delayInMs);
getScheduler().scheduleOnce(Duration.create(delayInMs, TimeUnit.MILLISECONDS), target, msg, getSystemDispatcher(), null);
}
@Data
@AllArgsConstructor
private static class ComponentConfiguration {
private final String clazz;
private final String name;
private final String configuration;
}
}

28
application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java

@ -19,6 +19,7 @@ import akka.actor.ActorContext;
import akka.event.LoggingAdapter;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.stats.StatsPersistTick;
import org.thingsboard.server.common.data.id.EntityId;
@ -26,25 +27,25 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.service.queue.MsgQueueService;
import javax.annotation.Nullable;
import java.util.function.Consumer;
@Slf4j
public abstract class ComponentMsgProcessor<T extends EntityId> extends AbstractContextAwareMsgProcessor {
protected final TenantId tenantId;
protected final T entityId;
protected final MsgQueueService queue;
protected ComponentLifecycleState state;
protected ComponentMsgProcessor(ActorSystemContext systemContext, LoggingAdapter logger, TenantId tenantId, T id) {
super(systemContext, logger);
protected ComponentMsgProcessor(ActorSystemContext systemContext, TenantId tenantId, T id) {
super(systemContext);
this.tenantId = tenantId;
this.entityId = id;
this.queue = systemContext.getMsgQueueService();
}
public abstract String getComponentName();
public abstract void start(ActorContext context) throws Exception;
public abstract void stop(ActorContext context) throws Exception;
@ -82,22 +83,9 @@ public abstract class ComponentMsgProcessor<T extends EntityId> extends Abstract
protected void checkActive() {
if (state != ComponentLifecycleState.ACTIVE) {
throw new IllegalStateException("Rule chain is not active!");
log.warn("Rule chain is not active. Current state [{}] for processor [{}] tenant [{}]", state, tenantId, entityId);
throw new IllegalStateException("Rule chain is not active! " + entityId + " - " + tenantId);
}
}
protected void putToQueue(final TbMsg tbMsg, final Consumer<TbMsg> onSuccess) {
EntityId entityId = tbMsg.getRuleNodeId() != null ? tbMsg.getRuleNodeId() : tbMsg.getRuleChainId();
Futures.addCallback(queue.put(this.tenantId, tbMsg, entityId.getId(), tbMsg.getClusterPartition()), new FutureCallback<Void>() {
@Override
public void onSuccess(@Nullable Void result) {
onSuccess.accept(tbMsg);
}
@Override
public void onFailure(Throwable t) {
logger.debug("Failed to push message [{}] to queue due to [{}]", tbMsg, t);
}
});
}
}

9
application/src/main/java/org/thingsboard/server/actors/shared/EntityActorsManager.java

@ -20,6 +20,8 @@ import akka.actor.ActorRef;
import akka.actor.Props;
import akka.actor.UntypedActor;
import akka.japi.Creator;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.service.ContextAwareActor;
@ -39,11 +41,11 @@ import java.util.Map;
public abstract class EntityActorsManager<T extends EntityId, A extends UntypedActor, M extends SearchTextBased<? extends UUIDBased>> {
protected final ActorSystemContext systemContext;
protected final Map<T, ActorRef> actors;
protected final BiMap<T, ActorRef> actors;
public EntityActorsManager(ActorSystemContext systemContext) {
this.systemContext = systemContext;
this.actors = new HashMap<>();
this.actors = HashBiMap.create();
}
protected abstract TenantId getTenantId();
@ -65,7 +67,8 @@ public abstract class EntityActorsManager<T extends EntityId, A extends UntypedA
}
}
public void visit(M entity, ActorRef actorRef) {}
public void visit(M entity, ActorRef actorRef) {
}
public ActorRef getOrCreateActor(ActorContext context, T entityId) {
return actors.computeIfAbsent(entityId, eId ->

2
application/src/main/java/org/thingsboard/server/actors/shared/rulechain/RuleChainManager.java

@ -50,7 +50,7 @@ public abstract class RuleChainManager extends EntityActorsManager<RuleChainId,
@Override
public void visit(RuleChain entity, ActorRef actorRef) {
if (entity.isRoot()) {
if (entity != null && entity.isRoot()) {
rootChain = entity;
rootChainActor = actorRef;
}

13
application/src/main/java/org/thingsboard/server/actors/stats/StatsActor.java

@ -15,10 +15,9 @@
*/
package org.thingsboard.server.actors.stats;
import akka.event.Logging;
import akka.event.LoggingAdapter;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.service.ContextAwareActor;
import org.thingsboard.server.actors.service.ContextBasedCreator;
@ -27,9 +26,9 @@ import org.thingsboard.server.common.data.Event;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
@Slf4j
public class StatsActor extends ContextAwareActor {
private final LoggingAdapter logger = Logging.getLogger(getContext().system(), this);
private final ObjectMapper mapper = new ObjectMapper();
public StatsActor(ActorSystemContext context) {
@ -43,13 +42,13 @@ public class StatsActor extends ContextAwareActor {
}
@Override
public void onReceive(Object msg) throws Exception {
logger.debug("Received message: {}", msg);
public void onReceive(Object msg) {
log.debug("Received message: {}", msg);
if (msg instanceof StatsPersistMsg) {
try {
onStatsPersistMsg((StatsPersistMsg) msg);
} catch (Exception e) {
logger.warning("Failed to persist statistics: {}", msg, e);
log.warn("Failed to persist statistics: {}", msg, e);
}
}
}
@ -75,7 +74,7 @@ public class StatsActor extends ContextAwareActor {
}
@Override
public StatsActor create() throws Exception {
public StatsActor create() {
return new StatsActor(context);
}
}

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

@ -17,15 +17,19 @@ package org.thingsboard.server.actors.tenant;
import akka.actor.ActorInitializationException;
import akka.actor.ActorRef;
import akka.actor.LocalActorRef;
import akka.actor.OneForOneStrategy;
import akka.actor.Props;
import akka.actor.SupervisorStrategy;
import akka.actor.Terminated;
import akka.japi.Function;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.device.DeviceActor;
import org.thingsboard.server.actors.device.DeviceActorCreator;
import org.thingsboard.server.actors.device.DeviceActorToRuleEngineMsg;
import org.thingsboard.server.actors.ruleChain.RuleChainManagerActor;
import org.thingsboard.server.actors.ruleChain.RuleChainToRuleChainMsg;
import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.actors.shared.rulechain.TenantRuleChainManager;
@ -33,6 +37,7 @@ import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.aware.DeviceAwareMsg;
@ -47,15 +52,14 @@ import java.util.Map;
public class TenantActor extends RuleChainManagerActor {
private final TenantId tenantId;
private final Map<DeviceId, ActorRef> deviceActors;
private final BiMap<DeviceId, ActorRef> deviceActors;
private TenantActor(ActorSystemContext systemContext, TenantId tenantId) {
super(systemContext, new TenantRuleChainManager(systemContext, tenantId));
this.tenantId = tenantId;
this.deviceActors = new HashMap<>();
this.deviceActors = HashBiMap.create();
}
@Override
public SupervisorStrategy supervisorStrategy() {
return strategy;
@ -63,15 +67,20 @@ public class TenantActor extends RuleChainManagerActor {
@Override
public void preStart() {
logger.info("[{}] Starting tenant actor.", tenantId);
log.info("[{}] Starting tenant actor.", tenantId);
try {
initRuleChains();
logger.info("[{}] Tenant actor started.", tenantId);
log.info("[{}] Tenant actor started.", tenantId);
} catch (Exception e) {
logger.error(e, "[{}] Unknown failure", tenantId);
log.warn("[{}] Unknown failure", tenantId, e);
}
}
@Override
public void postStop() {
log.info("[{}] Stopping tenant actor.", tenantId);
}
@Override
protected boolean process(TbActorMsg msg) {
switch (msg.getMsgType()) {
@ -87,7 +96,7 @@ public class TenantActor extends RuleChainManagerActor {
case DEVICE_ACTOR_TO_RULE_ENGINE_MSG:
onDeviceActorToRuleEngineMsg((DeviceActorToRuleEngineMsg) msg);
break;
case DEVICE_SESSION_TO_DEVICE_ACTOR_MSG:
case TRANSPORT_TO_DEVICE_ACTOR_MSG:
case DEVICE_ATTRIBUTES_UPDATE_TO_DEVICE_ACTOR_MSG:
case DEVICE_CREDENTIALS_UPDATE_TO_DEVICE_ACTOR_MSG:
case DEVICE_NAME_OR_TYPE_UPDATE_TO_DEVICE_ACTOR_MSG:
@ -105,29 +114,26 @@ public class TenantActor extends RuleChainManagerActor {
return true;
}
@Override
protected void broadcast(Object msg) {
super.broadcast(msg);
deviceActors.values().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender()));
}
private void onServiceToRuleEngineMsg(ServiceToRuleEngineMsg msg) {
if (ruleChainManager.getRootChainActor()!=null)
ruleChainManager.getRootChainActor().tell(msg, self());
else logger.info("[{}] No Root Chain", msg);
if (ruleChainManager.getRootChainActor() != null) {
ruleChainManager.getRootChainActor().tell(msg, self());
} else {
log.info("[{}] No Root Chain: {}", tenantId, msg);
}
}
private void onDeviceActorToRuleEngineMsg(DeviceActorToRuleEngineMsg msg) {
if (ruleChainManager.getRootChainActor()!=null)
ruleChainManager.getRootChainActor().tell(msg, self());
else logger.info("[{}] No Root Chain", msg);
if (ruleChainManager.getRootChainActor() != null) {
ruleChainManager.getRootChainActor().tell(msg, self());
} else {
log.info("[{}] No Root Chain: {}", tenantId, msg);
}
}
private void onRuleChainMsg(RuleChainAwareMsg msg) {
ruleChainManager.getOrCreateActor(context(), msg.getRuleChainId()).tell(msg, self());
}
private void onToDeviceActorMsg(DeviceAwareMsg msg) {
getOrCreateDeviceActor(msg.getDeviceId()).tell(msg, ActorRef.noSender());
}
@ -142,13 +148,35 @@ public class TenantActor extends RuleChainManagerActor {
}
target.tell(msg, ActorRef.noSender());
} else {
logger.debug("Invalid component lifecycle msg: {}", msg);
log.debug("[{}] Invalid component lifecycle msg: {}", tenantId, msg);
}
}
private ActorRef getOrCreateDeviceActor(DeviceId deviceId) {
return deviceActors.computeIfAbsent(deviceId, k -> context().actorOf(Props.create(new DeviceActor.ActorCreator(systemContext, tenantId, deviceId))
.withDispatcher(DefaultActorService.CORE_DISPATCHER_NAME), deviceId.toString()));
return deviceActors.computeIfAbsent(deviceId, k -> {
log.debug("[{}][{}] Creating device actor.", tenantId, deviceId);
ActorRef deviceActor = context().actorOf(Props.create(new DeviceActorCreator(systemContext, tenantId, deviceId))
.withDispatcher(DefaultActorService.CORE_DISPATCHER_NAME)
, deviceId.toString());
context().watch(deviceActor);
log.debug("[{}][{}] Created device actor: {}.", tenantId, deviceId, deviceActor);
return deviceActor;
});
}
@Override
protected void processTermination(Terminated message) {
ActorRef terminated = message.actor();
if (terminated instanceof LocalActorRef) {
boolean removed = deviceActors.inverse().remove(terminated) != null;
if (removed) {
log.debug("[{}] Removed actor:", terminated);
} else {
log.warn("[{}] Removed actor was not found in the device map!");
}
} else {
throw new IllegalStateException("Remote actors are not supported!");
}
}
public static class ActorCreator extends ContextBasedCreator<TenantActor> {
@ -162,7 +190,7 @@ public class TenantActor extends RuleChainManagerActor {
}
@Override
public TenantActor create() throws Exception {
public TenantActor create() {
return new TenantActor(context, tenantId);
}
}
@ -170,8 +198,8 @@ public class TenantActor extends RuleChainManagerActor {
private final SupervisorStrategy strategy = new OneForOneStrategy(3, Duration.create("1 minute"), new Function<Throwable, SupervisorStrategy.Directive>() {
@Override
public SupervisorStrategy.Directive apply(Throwable t) {
logger.error(t, "Unknown failure");
if(t instanceof ActorInitializationException){
log.warn("[{}] Unknown failure", tenantId, t);
if (t instanceof ActorInitializationException) {
return SupervisorStrategy.stop();
} else {
return SupervisorStrategy.resume();

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

@ -83,7 +83,7 @@ public class AlarmController extends BaseController {
Alarm savedAlarm = checkNotNull(alarmService.createOrUpdateAlarm(alarm));
logEntityAction(savedAlarm.getId(), savedAlarm,
getCurrentUser().getCustomerId(),
savedAlarm.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
alarm.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
return savedAlarm;
} catch (Exception e) {
logEntityAction(emptyId(EntityType.ALARM), alarm,

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

@ -19,9 +19,11 @@ import com.datastax.driver.core.utils.UUIDs;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ -48,14 +50,17 @@ import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.cluster.SendToClusterMsg;
import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.audit.AuditLogService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.ModelConstants;
@ -69,6 +74,7 @@ import org.thingsboard.server.exception.ThingsboardErrorResponseHandler;
import org.thingsboard.server.service.component.ComponentDiscoveryService;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.state.DeviceStateService;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import javax.mail.MessagingException;
import javax.servlet.http.HttpServletRequest;
@ -139,6 +145,20 @@ public abstract class BaseController {
@Autowired
protected DeviceStateService deviceStateService;
@Autowired
protected EntityViewService entityViewService;
@Autowired
protected TelemetrySubscriptionService tsSubService;
@Autowired
protected AttributesService attributesService;
@Value("${server.log_controller_error_stack_trace}")
@Getter
private boolean logControllerErrorStackTrace;
@ExceptionHandler(ThingsboardException.class)
public void handleThingsboardException(ThingsboardException ex, HttpServletResponse response) {
errorResponseHandler.handle(ex, response);
@ -149,7 +169,7 @@ public abstract class BaseController {
}
private ThingsboardException handleException(Exception exception, boolean logException) {
if (logException) {
if (logException && logControllerErrorStackTrace) {
log.error("Error [{}]", exception.getMessage(), exception);
}
@ -247,7 +267,6 @@ public abstract class BaseController {
Customer checkCustomerId(CustomerId customerId) throws ThingsboardException {
try {
validateId(customerId, "Incorrect customerId " + customerId);
SecurityUser authUser = getCurrentUser();
if (authUser.getAuthority() == Authority.SYS_ADMIN ||
(authUser.getAuthority() != Authority.TENANT_ADMIN &&
@ -255,9 +274,13 @@ public abstract class BaseController {
throw new ThingsboardException(YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION,
ThingsboardErrorCode.PERMISSION_DENIED);
}
Customer customer = customerService.findCustomerById(customerId);
checkCustomer(customer);
return customer;
if (customerId != null && !customerId.isNullUid()) {
Customer customer = customerService.findCustomerById(customerId);
checkCustomer(customer);
return customer;
} else {
return null;
}
} catch (Exception e) {
throw handleException(e, false);
}
@ -313,6 +336,9 @@ public abstract class BaseController {
case USER:
checkUserId(new UserId(entityId.getId()));
return;
case ENTITY_VIEW:
checkEntityViewId(new EntityViewId(entityId.getId()));
return;
default:
throw new IllegalArgumentException("Unsupported entity type: " + entityId.getEntityType());
}
@ -335,11 +361,26 @@ public abstract class BaseController {
protected void checkDevice(Device device) throws ThingsboardException {
checkNotNull(device);
checkTenantId(device.getTenantId());
if (device.getCustomerId() != null && !device.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
checkCustomerId(device.getCustomerId());
checkCustomerId(device.getCustomerId());
}
protected EntityView checkEntityViewId(EntityViewId entityViewId) throws ThingsboardException {
try {
validateId(entityViewId, "Incorrect entityViewId " + entityViewId);
EntityView entityView = entityViewService.findEntityViewById(entityViewId);
checkEntityView(entityView);
return entityView;
} catch (Exception e) {
throw handleException(e, false);
}
}
protected void checkEntityView(EntityView entityView) throws ThingsboardException {
checkNotNull(entityView);
checkTenantId(entityView.getTenantId());
checkCustomerId(entityView.getCustomerId());
}
Asset checkAssetId(AssetId assetId) throws ThingsboardException {
try {
validateId(assetId, "Incorrect assetId " + assetId);
@ -354,9 +395,7 @@ public abstract class BaseController {
protected void checkAsset(Asset asset) throws ThingsboardException {
checkNotNull(asset);
checkTenantId(asset.getTenantId());
if (asset.getCustomerId() != null && !asset.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
checkCustomerId(asset.getCustomerId());
}
checkCustomerId(asset.getCustomerId());
}
Alarm checkAlarmId(AlarmId alarmId) throws ThingsboardException {
@ -465,8 +504,7 @@ public abstract class BaseController {
ComponentDescriptor checkComponentDescriptorByClazz(String clazz) throws ThingsboardException {
try {
log.debug("[{}] Lookup component descriptor", clazz);
ComponentDescriptor componentDescriptor = checkNotNull(componentDescriptorService.getComponent(clazz));
return componentDescriptor;
return checkNotNull(componentDescriptorService.getComponent(clazz));
} catch (Exception e) {
throw handleException(e, false);
}
@ -530,16 +568,16 @@ public abstract class BaseController {
}
protected <I extends EntityId> I emptyId(EntityType entityType) {
return (I)EntityIdFactory.getByTypeAndUuid(entityType, ModelConstants.NULL_UUID);
return (I) EntityIdFactory.getByTypeAndUuid(entityType, ModelConstants.NULL_UUID);
}
protected <E extends HasName, I extends EntityId> void logEntityAction(I entityId, E entity, CustomerId customerId,
ActionType actionType, Exception e, Object... additionalInfo) throws ThingsboardException {
ActionType actionType, Exception e, Object... additionalInfo) throws ThingsboardException {
logEntityAction(getCurrentUser(), entityId, entity, customerId, actionType, e, additionalInfo);
}
protected <E extends HasName, I extends EntityId> void logEntityAction(User user, I entityId, E entity, CustomerId customerId,
ActionType actionType, Exception e, Object... additionalInfo) throws ThingsboardException {
ActionType actionType, Exception e, Object... additionalInfo) throws ThingsboardException {
if (customerId == null || customerId.isNullUid()) {
customerId = user.getCustomerId();
}
@ -555,7 +593,7 @@ public abstract class BaseController {
}
private <E extends HasName, I extends EntityId> void pushEntityActionToRuleEngine(I entityId, E entity, User user, CustomerId customerId,
ActionType actionType, Object... additionalInfo) {
ActionType actionType, Object... additionalInfo) {
String msgType = null;
switch (actionType) {
case ADDED:
@ -579,6 +617,12 @@ public abstract class BaseController {
case ATTRIBUTES_DELETED:
msgType = DataConstants.ATTRIBUTES_DELETED;
break;
case ALARM_ACK:
msgType = DataConstants.ALARM_ACK;
break;
case ALARM_CLEAR:
msgType = DataConstants.ALARM_CLEAR;
break;
}
if (!StringUtils.isEmpty(msgType)) {
try {
@ -628,7 +672,7 @@ public abstract class BaseController {
String scope = extractParameter(String.class, 0, additionalInfo);
List<String> keys = extractParameter(List.class, 1, additionalInfo);
metaData.putValue("scope", scope);
ArrayNode attrsArrayNode = entityNode.putArray("attributes");
ArrayNode attrsArrayNode = entityNode.putArray("attributes");
if (keys != null) {
keys.forEach(attrsArrayNode::add);
}
@ -637,7 +681,7 @@ public abstract class BaseController {
TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), msgType, entityId, metaData, TbMsgDataType.JSON
, json.writeValueAsString(entityNode)
, null, null, 0L);
actorService.onMsg(new ServiceToRuleEngineMsg(user.getTenantId(), tbMsg));
actorService.onMsg(new SendToClusterMsg(entityId, new ServiceToRuleEngineMsg(user.getTenantId(), tbMsg)));
} catch (Exception e) {
log.warn("[{}] Failed to push entity action to rule engine: {}", entityId, actionType, e);
}

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

@ -15,6 +15,8 @@
*/
package org.thingsboard.server.controller;
import lombok.Getter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
@ -49,6 +51,10 @@ public class DashboardController extends BaseController {
public static final String DASHBOARD_ID = "dashboardId";
@Value("${dashboard.max_datapoints_limit}")
private long maxDatapointsLimit;
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/dashboard/serverTime", method = RequestMethod.GET)
@ResponseBody
@ -56,6 +62,13 @@ public class DashboardController extends BaseController {
return System.currentTimeMillis();
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/dashboard/maxDatapointsLimit", method = RequestMethod.GET)
@ResponseBody
public long getMaxDatapointsLimit() throws ThingsboardException {
return maxDatapointsLimit;
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/dashboard/info/{dashboardId}", method = RequestMethod.GET)
@ResponseBody

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

@ -0,0 +1,336 @@
/**
* Copyright © 2016-2018 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 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 org.springframework.http.HttpStatus;
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.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.service.security.model.SecurityUser;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import static org.thingsboard.server.controller.CustomerController.CUSTOMER_ID;
/**
* Created by Victor Basanets on 8/28/2017.
*/
@RestController
@RequestMapping("/api")
@Slf4j
public class EntityViewController extends BaseController {
public static final String ENTITY_VIEW_ID = "entityViewId";
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/entityView/{entityViewId}", method = RequestMethod.GET)
@ResponseBody
public EntityView getEntityViewById(@PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException {
checkParameter(ENTITY_VIEW_ID, strEntityViewId);
try {
return checkEntityViewId(new EntityViewId(toUUID(strEntityViewId)));
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/entityView", method = RequestMethod.POST)
@ResponseBody
public EntityView saveEntityView(@RequestBody EntityView entityView) throws ThingsboardException {
try {
entityView.setTenantId(getCurrentUser().getTenantId());
EntityView savedEntityView = checkNotNull(entityViewService.saveEntityView(entityView));
List<ListenableFuture<List<Void>>> futures = new ArrayList<>();
if (savedEntityView.getKeys() != null && savedEntityView.getKeys().getAttributes() != null) {
futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.CLIENT_SCOPE, savedEntityView.getKeys().getAttributes().getCs(), getCurrentUser()));
futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.SERVER_SCOPE, savedEntityView.getKeys().getAttributes().getSs(), getCurrentUser()));
futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.SHARED_SCOPE, savedEntityView.getKeys().getAttributes().getSh(), getCurrentUser()));
}
for (ListenableFuture<List<Void>> future : futures) {
try {
future.get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException("Failed to copy attributes to entity view", e);
}
}
logEntityAction(savedEntityView.getId(), savedEntityView, null,
entityView.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
return savedEntityView;
} catch (Exception e) {
logEntityAction(emptyId(EntityType.ENTITY_VIEW), entityView, null,
entityView.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e);
throw handleException(e);
}
}
private ListenableFuture<List<Void>> copyAttributesFromEntityToEntityView(EntityView entityView, String scope, Collection<String> keys, SecurityUser user) throws ThingsboardException {
EntityViewId entityId = entityView.getId();
if (keys != null && !keys.isEmpty()) {
ListenableFuture<List<AttributeKvEntry>> getAttrFuture = attributesService.find(entityView.getEntityId(), scope, keys);
return Futures.transform(getAttrFuture, attributeKvEntries -> {
List<AttributeKvEntry> attributes;
if (attributeKvEntries != null && !attributeKvEntries.isEmpty()) {
attributes =
attributeKvEntries.stream()
.filter(attributeKvEntry -> {
long startTime = entityView.getStartTimeMs();
long endTime = entityView.getEndTimeMs();
long lastUpdateTs = attributeKvEntry.getLastUpdateTs();
return startTime == 0 && endTime == 0 ||
(endTime == 0 && startTime < lastUpdateTs) ||
(startTime == 0 && endTime > lastUpdateTs)
? true : startTime < lastUpdateTs && endTime > lastUpdateTs;
}).collect(Collectors.toList());
tsSubService.saveAndNotify(entityId, scope, attributes, new FutureCallback<Void>() {
@Override
public void onSuccess(@Nullable Void tmp) {
try {
logAttributesUpdated(user, entityId, scope, attributes, null);
} catch (ThingsboardException e) {
log.error("Failed to log attribute updates", e);
}
}
@Override
public void onFailure(Throwable t) {
try {
logAttributesUpdated(user, entityId, scope, attributes, t);
} catch (ThingsboardException e) {
log.error("Failed to log attribute updates", e);
}
}
});
}
return null;
});
} else {
return Futures.immediateFuture(null);
}
}
private void logAttributesUpdated(SecurityUser user, EntityId entityId, String scope, List<AttributeKvEntry> attributes, Throwable e) throws ThingsboardException {
logEntityAction(user, (UUIDBased & EntityId) entityId, null, null, ActionType.ATTRIBUTES_UPDATED, toException(e),
scope, attributes);
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/entityView/{entityViewId}", method = RequestMethod.DELETE)
@ResponseStatus(value = HttpStatus.OK)
public void deleteEntityView(@PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException {
checkParameter(ENTITY_VIEW_ID, strEntityViewId);
try {
EntityViewId entityViewId = new EntityViewId(toUUID(strEntityViewId));
EntityView entityView = checkEntityViewId(entityViewId);
entityViewService.deleteEntityView(entityViewId);
logEntityAction(entityViewId, entityView, entityView.getCustomerId(),
ActionType.DELETED, null, strEntityViewId);
} catch (Exception e) {
logEntityAction(emptyId(EntityType.ENTITY_VIEW),
null,
null,
ActionType.DELETED, e, strEntityViewId);
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/entityViews", params = {"entityViewName"}, method = RequestMethod.GET)
@ResponseBody
public EntityView getTenantEntityView(
@RequestParam String entityViewName) throws ThingsboardException {
try {
TenantId tenantId = getCurrentUser().getTenantId();
return checkNotNull(entityViewService.findEntityViewByTenantIdAndName(tenantId, entityViewName));
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/{customerId}/entityView/{entityViewId}", method = RequestMethod.POST)
@ResponseBody
public EntityView assignEntityViewToCustomer(@PathVariable(CUSTOMER_ID) String strCustomerId,
@PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException {
checkParameter(CUSTOMER_ID, strCustomerId);
checkParameter(ENTITY_VIEW_ID, strEntityViewId);
try {
CustomerId customerId = new CustomerId(toUUID(strCustomerId));
Customer customer = checkCustomerId(customerId);
EntityViewId entityViewId = new EntityViewId(toUUID(strEntityViewId));
checkEntityViewId(entityViewId);
EntityView savedEntityView = checkNotNull(entityViewService.assignEntityViewToCustomer(entityViewId, customerId));
logEntityAction(entityViewId, savedEntityView,
savedEntityView.getCustomerId(),
ActionType.ASSIGNED_TO_CUSTOMER, null, strEntityViewId, strCustomerId, customer.getName());
return savedEntityView;
} catch (Exception e) {
logEntityAction(emptyId(EntityType.ENTITY_VIEW), null,
null,
ActionType.ASSIGNED_TO_CUSTOMER, e, strEntityViewId, strCustomerId);
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/entityView/{entityViewId}", method = RequestMethod.DELETE)
@ResponseBody
public EntityView unassignEntityViewFromCustomer(@PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException {
checkParameter(ENTITY_VIEW_ID, strEntityViewId);
try {
EntityViewId entityViewId = new EntityViewId(toUUID(strEntityViewId));
EntityView entityView = checkEntityViewId(entityViewId);
if (entityView.getCustomerId() == null || entityView.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
throw new IncorrectParameterException("Entity View isn't assigned to any customer!");
}
Customer customer = checkCustomerId(entityView.getCustomerId());
EntityView savedEntityView = checkNotNull(entityViewService.unassignEntityViewFromCustomer(entityViewId));
logEntityAction(entityViewId, entityView,
entityView.getCustomerId(),
ActionType.UNASSIGNED_FROM_CUSTOMER, null, strEntityViewId, customer.getId().toString(), customer.getName());
return savedEntityView;
} catch (Exception e) {
logEntityAction(emptyId(EntityType.ENTITY_VIEW), null,
null,
ActionType.UNASSIGNED_FROM_CUSTOMER, e, strEntityViewId);
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/customer/{customerId}/entityViews", params = {"limit"}, method = RequestMethod.GET)
@ResponseBody
public TextPageData<EntityView> getCustomerEntityViews(
@PathVariable("customerId") String strCustomerId,
@RequestParam int limit,
@RequestParam(required = false) String type,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String idOffset,
@RequestParam(required = false) String textOffset) throws ThingsboardException {
checkParameter("customerId", strCustomerId);
try {
TenantId tenantId = getCurrentUser().getTenantId();
CustomerId customerId = new CustomerId(toUUID(strCustomerId));
checkCustomerId(customerId);
TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
if (type != null && type.trim().length() > 0) {
return checkNotNull(entityViewService.findEntityViewsByTenantIdAndCustomerIdAndType(tenantId, customerId, pageLink, type));
} else {
return checkNotNull(entityViewService.findEntityViewsByTenantIdAndCustomerId(tenantId, customerId, pageLink));
}
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/entityViews", params = {"limit"}, method = RequestMethod.GET)
@ResponseBody
public TextPageData<EntityView> getTenantEntityViews(
@RequestParam int limit,
@RequestParam(required = false) String type,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String idOffset,
@RequestParam(required = false) String textOffset) throws ThingsboardException {
try {
TenantId tenantId = getCurrentUser().getTenantId();
TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
if (type != null && type.trim().length() > 0) {
return checkNotNull(entityViewService.findEntityViewByTenantIdAndType(tenantId, pageLink, type));
} else {
return checkNotNull(entityViewService.findEntityViewByTenantId(tenantId, pageLink));
}
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/entityViews", method = RequestMethod.POST)
@ResponseBody
public List<EntityView> findByQuery(@RequestBody EntityViewSearchQuery query) throws ThingsboardException {
checkNotNull(query);
checkNotNull(query.getParameters());
checkNotNull(query.getEntityViewTypes());
checkEntityId(query.getParameters().getEntityId());
try {
List<EntityView> entityViews = checkNotNull(entityViewService.findEntityViewsByQuery(query).get());
entityViews = entityViews.stream().filter(entityView -> {
try {
checkEntityView(entityView);
return true;
} catch (ThingsboardException e) {
return false;
}
}).collect(Collectors.toList());
return entityViews;
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/entityView/types", method = RequestMethod.GET)
@ResponseBody
public List<EntitySubtype> getEntityViewTypes() throws ThingsboardException {
try {
SecurityUser user = getCurrentUser();
TenantId tenantId = user.getTenantId();
ListenableFuture<List<EntitySubtype>> entityViewTypes = entityViewService.findEntityViewTypesByTenantId(tenantId);
return checkNotNull(entityViewTypes.get());
} catch (Exception e) {
throw handleException(e);
}
}
}

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

@ -50,7 +50,7 @@ import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.service.script.JsSandboxService;
import org.thingsboard.server.service.script.JsInvokeService;
import org.thingsboard.server.service.script.RuleNodeJsScriptEngine;
import java.util.List;
@ -71,7 +71,7 @@ public class RuleChainController extends BaseController {
private EventService eventService;
@Autowired
private JsSandboxService jsSandboxService;
private JsInvokeService jsInvokeService;
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/ruleChain/{ruleChainId}", method = RequestMethod.GET)
@ -276,7 +276,7 @@ public class RuleChainController extends BaseController {
String errorText = "";
ScriptEngine engine = null;
try {
engine = new RuleNodeJsScriptEngine(jsSandboxService, script, argNames);
engine = new RuleNodeJsScriptEngine(jsInvokeService, getCurrentUser().getId(), script, argNames);
TbMsg inMsg = new TbMsg(UUIDs.timeBased(), msgType, null, new TbMsgMetaData(metadata), data, null, null, 0L);
switch (scriptType) {
case "update":

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

@ -49,9 +49,11 @@ import org.thingsboard.server.common.data.kv.Aggregation;
import org.thingsboard.server.common.data.kv.AttributeKey;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseDeleteTsKvQuery;
import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
import org.thingsboard.server.common.data.kv.DoubleDataEntry;
import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry;
@ -59,14 +61,11 @@ import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.msg.cluster.SendToClusterMsg;
import org.thingsboard.server.common.msg.core.TelemetryUploadRequest;
import org.thingsboard.server.common.transport.adaptor.JsonConverter;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.service.security.AccessValidator;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.telemetry.AttributeData;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import org.thingsboard.server.service.telemetry.TsData;
import org.thingsboard.server.service.telemetry.exception.InvalidParametersException;
import org.thingsboard.server.service.telemetry.exception.UncheckedApiException;
@ -93,15 +92,9 @@ import java.util.stream.Collectors;
@Slf4j
public class TelemetryController extends BaseController {
@Autowired
private AttributesService attributesService;
@Autowired
private TimeseriesService tsService;
@Autowired
private TelemetrySubscriptionService tsSubService;
@Autowired
private AccessValidator accessValidator;
@ -256,6 +249,60 @@ public class TelemetryController extends BaseController {
return saveTelemetry(entityId, requestBody, ttl);
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/{entityType}/{entityId}/timeseries/delete", method = RequestMethod.DELETE)
@ResponseBody
public DeferredResult<ResponseEntity> deleteEntityTimeseries(@PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr,
@RequestParam(name = "keys") String keysStr,
@RequestParam(name = "deleteAllDataForKeys", defaultValue = "false") boolean deleteAllDataForKeys,
@RequestParam(name = "startTs", required = false) Long startTs,
@RequestParam(name = "endTs", required = false) Long endTs,
@RequestParam(name = "rewriteLatestIfDeleted", defaultValue = "false") boolean rewriteLatestIfDeleted) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
return deleteTimeseries(entityId, keysStr, deleteAllDataForKeys, startTs, endTs, rewriteLatestIfDeleted);
}
private DeferredResult<ResponseEntity> deleteTimeseries(EntityId entityIdStr, String keysStr, boolean deleteAllDataForKeys,
Long startTs, Long endTs, boolean rewriteLatestIfDeleted) throws ThingsboardException {
List<String> keys = toKeysList(keysStr);
if (keys.isEmpty()) {
return getImmediateDeferredResult("Empty keys: " + keysStr, HttpStatus.BAD_REQUEST);
}
SecurityUser user = getCurrentUser();
long deleteFromTs;
long deleteToTs;
if (deleteAllDataForKeys) {
deleteFromTs = 0L;
deleteToTs = System.currentTimeMillis();
} else {
deleteFromTs = startTs;
deleteToTs = endTs;
}
return accessValidator.validateEntityAndCallback(user, entityIdStr, (result, entityId) -> {
List<DeleteTsKvQuery> deleteTsKvQueries = new ArrayList<>();
for (String key : keys) {
deleteTsKvQueries.add(new BaseDeleteTsKvQuery(key, deleteFromTs, deleteToTs, rewriteLatestIfDeleted));
}
ListenableFuture<List<Void>> future = tsService.remove(entityId, deleteTsKvQueries);
Futures.addCallback(future, new FutureCallback<List<Void>>() {
@Override
public void onSuccess(@Nullable List<Void> tmp) {
logTimeseriesDeleted(user, entityId, keys, null);
result.setResult(new ResponseEntity<>(HttpStatus.OK));
}
@Override
public void onFailure(Throwable t) {
logTimeseriesDeleted(user, entityId, keys, t);
result.setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
}
}, executor);
});
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/{deviceId}/{scope}", method = RequestMethod.DELETE)
@ResponseBody
@ -352,7 +399,7 @@ public class TelemetryController extends BaseController {
}
private DeferredResult<ResponseEntity> saveTelemetry(EntityId entityIdSrc, String requestBody, long ttl) throws ThingsboardException {
TelemetryUploadRequest telemetryRequest;
Map<Long, List<KvEntry>> telemetryRequest;
JsonElement telemetryJson;
try {
telemetryJson = new JsonParser().parse(requestBody);
@ -360,12 +407,12 @@ public class TelemetryController extends BaseController {
return getImmediateDeferredResult("Unable to parse timeseries payload: Invalid JSON body!", HttpStatus.BAD_REQUEST);
}
try {
telemetryRequest = JsonConverter.convertToTelemetry(telemetryJson);
telemetryRequest = JsonConverter.convertToTelemetry(telemetryJson, System.currentTimeMillis());
} catch (Exception e) {
return getImmediateDeferredResult("Unable to parse timeseries payload. Invalid JSON body: " + e.getMessage(), HttpStatus.BAD_REQUEST);
}
List<TsKvEntry> entries = new ArrayList<>();
for (Map.Entry<Long, List<KvEntry>> entry : telemetryRequest.getData().entrySet()) {
for (Map.Entry<Long, List<KvEntry>> entry : telemetryRequest.entrySet()) {
for (KvEntry kv : entry.getValue()) {
entries.add(new BasicTsKvEntry(entry.getKey(), kv));
}
@ -513,6 +560,15 @@ public class TelemetryController extends BaseController {
};
}
private void logTimeseriesDeleted(SecurityUser user, EntityId entityId, List<String> keys, Throwable e) {
try {
logEntityAction(user, (UUIDBased & EntityId) entityId, null, null, ActionType.TIMESERIES_DELETED, toException(e),
keys);
} catch (ThingsboardException te) {
log.warn("Failed to log timeseries delete", te);
}
}
private void logAttributesDeleted(SecurityUser user, EntityId entityId, String scope, List<String> keys, Throwable e) {
try {
logEntityAction(user, (UUIDBased & EntityId) entityId, null, null, ActionType.ATTRIBUTES_DELETED, toException(e),

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

@ -32,6 +32,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.service.install.InstallScripts;
@ -84,6 +85,8 @@ public class TenantController extends BaseController {
try {
TenantId tenantId = new TenantId(toUUID(strTenantId));
tenantService.deleteTenant(tenantId);
actorService.onEntityStateChange(tenantId, tenantId, ComponentLifecycleEvent.DELETED);
} catch (Exception e) {
throw handleException(e);
}

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

@ -24,9 +24,10 @@ import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.server.service.component.ComponentDiscoveryService;
import org.thingsboard.server.service.install.DataUpdateService;
import org.thingsboard.server.service.install.DatabaseSchemaService;
import org.thingsboard.server.service.install.DatabaseUpgradeService;
import org.thingsboard.server.service.install.EntityDatabaseSchemaService;
import org.thingsboard.server.service.install.SystemDataLoaderService;
import org.thingsboard.server.service.install.TsDatabaseSchemaService;
@Service
@Profile("install")
@ -43,7 +44,10 @@ public class ThingsboardInstallService {
private Boolean loadDemo;
@Autowired
private DatabaseSchemaService databaseSchemaService;
private EntityDatabaseSchemaService entityDatabaseSchemaService;
@Autowired
private TsDatabaseSchemaService tsDatabaseSchemaService;
@Autowired
private DatabaseUpgradeService databaseUpgradeService;
@ -88,6 +92,20 @@ public class ThingsboardInstallService {
dataUpdateService.updateData("1.4.0");
case "2.0.0":
log.info("Upgrading ThingsBoard from version 2.0.0 to 2.1.1 ...");
databaseUpgradeService.upgradeDatabase("2.0.0");
case "2.1.1":
log.info("Upgrading ThingsBoard from version 2.1.1 to 2.1.2 ...");
databaseUpgradeService.upgradeDatabase("2.1.1");
case "2.1.3":
log.info("Upgrading ThingsBoard from version 2.1.3 to 2.2.0 ...");
databaseUpgradeService.upgradeDatabase("2.1.3");
log.info("Updating system data...");
systemDataLoaderService.deleteSystemWidgetBundle("charts");
@ -114,9 +132,13 @@ public class ThingsboardInstallService {
log.info("Starting ThingsBoard Installation...");
log.info("Installing DataBase schema...");
log.info("Installing DataBase schema for entities...");
entityDatabaseSchemaService.createDatabaseSchema();
log.info("Installing DataBase schema for timeseries...");
databaseSchemaService.createDatabaseSchema();
tsDatabaseSchemaService.createDatabaseSchema();
log.info("Loading system data...");

6
application/src/main/java/org/thingsboard/server/service/cluster/discovery/CurrentServerInstanceService.java

@ -19,7 +19,8 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.thingsboard.server.gen.discovery.ServerInstanceProtos;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.cluster.ServerType;
import javax.annotation.PostConstruct;
@ -43,8 +44,7 @@ public class CurrentServerInstanceService implements ServerInstanceService {
public void init() {
Assert.hasLength(rpcHost, missingProperty("rpc.bind_host"));
Assert.notNull(rpcPort, missingProperty("rpc.bind_port"));
self = new ServerInstance(ServerInstanceProtos.ServerInfo.newBuilder().setHost(rpcHost).setPort(rpcPort).setTs(System.currentTimeMillis()).build());
self = new ServerInstance(new ServerAddress(rpcHost, rpcPort, ServerType.CORE));
log.info("Current server instance: [{};{}]", self.getHost(), self.getPort());
}

4
application/src/main/java/org/thingsboard/server/service/cluster/discovery/DiscoveryService.java

@ -30,8 +30,4 @@ public interface DiscoveryService {
List<ServerInstance> getOtherServers();
boolean addListener(DiscoveryServiceListener listener);
boolean removeListener(DiscoveryServiceListener listener);
}

9
application/src/main/java/org/thingsboard/server/service/cluster/discovery/DummyDiscoveryService.java

@ -16,6 +16,7 @@
package org.thingsboard.server.service.cluster.discovery;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.DependsOn;
@ -62,13 +63,5 @@ public class DummyDiscoveryService implements DiscoveryService {
return Collections.emptyList();
}
@Override
public boolean addListener(DiscoveryServiceListener listener) {
return false;
}
@Override
public boolean removeListener(DiscoveryServiceListener listener) {
return false;
}
}

7
application/src/main/java/org/thingsboard/server/service/cluster/discovery/ServerInstance.java

@ -19,7 +19,6 @@ import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.gen.discovery.ServerInstanceProtos;
/**
* @author Andrew Shvayka
@ -41,12 +40,6 @@ public final class ServerInstance implements Comparable<ServerInstance> {
this.port = serverAddress.getPort();
}
public ServerInstance(ServerInstanceProtos.ServerInfo serverInfo) {
this.host = serverInfo.getHost();
this.port = serverInfo.getPort();
this.serverAddress = new ServerAddress(host, port);
}
@Override
public int compareTo(ServerInstance o) {
return this.serverAddress.compareTo(o.serverAddress);

130
application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java

@ -16,17 +16,22 @@
package org.thingsboard.server.service.cluster.discovery;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.SerializationException;
import org.apache.commons.lang3.SerializationUtils;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.imps.CuratorFrameworkState;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
import org.apache.curator.framework.state.ConnectionState;
import org.apache.curator.framework.state.ConnectionStateListener;
import org.apache.curator.retry.RetryForever;
import org.apache.curator.utils.CloseableUtils;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@ -35,7 +40,9 @@ import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.thingsboard.server.actors.service.ActorService;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.service.cluster.routing.ClusterRoutingService;
import org.thingsboard.server.service.state.DeviceStateService;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import org.thingsboard.server.utils.MiscUtils;
@ -44,9 +51,10 @@ import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;
import static org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent.Type.CHILD_REMOVED;
/**
* @author Andrew Shvayka
*/
@ -79,12 +87,19 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi
@Lazy
private DeviceStateService deviceStateService;
private final List<DiscoveryServiceListener> listeners = new CopyOnWriteArrayList<>();
@Autowired
@Lazy
private ActorService actorService;
@Autowired
@Lazy
private ClusterRoutingService routingService;
private CuratorFramework client;
private PathChildrenCache cache;
private String nodePath;
private volatile boolean stopped = false;
@PostConstruct
public void init() {
@ -106,6 +121,7 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi
cache.start();
} catch (Exception e) {
log.error("Failed to connect to ZK: {}", e.getMessage(), e);
CloseableUtils.closeQuietly(cache);
CloseableUtils.closeQuietly(client);
throw new RuntimeException(e);
}
@ -113,23 +129,74 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi
@PreDestroy
public void destroy() {
stopped = true;
unpublishCurrentServer();
CloseableUtils.closeQuietly(cache);
CloseableUtils.closeQuietly(client);
log.info("Stopped discovery service");
}
@Override
public void publishCurrentServer() {
public synchronized void publishCurrentServer() {
ServerInstance self = this.serverInstance.getSelf();
if (currentServerExists()) {
log.info("[{}:{}] ZK node for current instance already exists, NOT created new one: {}", self.getHost(), self.getPort(), nodePath);
} else {
try {
log.info("[{}:{}] Creating ZK node for current instance", self.getHost(), self.getPort());
nodePath = client.create()
.creatingParentsIfNeeded()
.withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(zkNodesDir + "/", SerializationUtils.serialize(self.getServerAddress()));
log.info("[{}:{}] Created ZK node for current instance: {}", self.getHost(), self.getPort(), nodePath);
client.getConnectionStateListenable().addListener(checkReconnect(self));
} catch (Exception e) {
log.error("Failed to create ZK node", e);
throw new RuntimeException(e);
}
}
}
private boolean currentServerExists() {
if (nodePath == null) {
return false;
}
try {
ServerInstance self = this.serverInstance.getSelf();
log.info("[{}:{}] Creating ZK node for current instance", self.getHost(), self.getPort());
nodePath = client.create()
.creatingParentsIfNeeded()
.withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(zkNodesDir + "/", SerializationUtils.serialize(self.getServerAddress()));
log.info("[{}:{}] Created ZK node for current instance: {}", self.getHost(), self.getPort(), nodePath);
ServerAddress registeredServerAdress = null;
registeredServerAdress = SerializationUtils.deserialize(client.getData().forPath(nodePath));
if (self.getServerAddress() != null && self.getServerAddress().equals(registeredServerAdress)) {
return true;
}
} catch (KeeperException.NoNodeException e) {
log.info("ZK node does not exist: {}", nodePath);
} catch (Exception e) {
log.error("Failed to create ZK node", e);
throw new RuntimeException(e);
log.error("Couldn't check if ZK node exists", e);
}
return false;
}
private ConnectionStateListener checkReconnect(ServerInstance self) {
return (client, newState) -> {
log.info("[{}:{}] ZK state changed: {}", self.getHost(), self.getPort(), newState);
if (newState == ConnectionState.LOST) {
reconnect();
}
};
}
private boolean reconnectInProgress = false;
private synchronized void reconnect() {
if (!reconnectInProgress) {
reconnectInProgress = true;
try {
client.blockUntilConnected();
publishCurrentServer();
} catch (InterruptedException e) {
log.error("Failed to reconnect to ZK: {}", e.getMessage(), e);
} finally {
reconnectInProgress = false;
}
}
}
@ -156,7 +223,7 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi
.filter(cd -> !cd.getPath().equals(nodePath))
.map(cd -> {
try {
return new ServerInstance( (ServerAddress) SerializationUtils.deserialize(cd.getData()));
return new ServerInstance((ServerAddress) SerializationUtils.deserialize(cd.getData()));
} catch (NoSuchElementException e) {
log.error("Failed to decode ZK node", e);
throw new RuntimeException(e);
@ -165,18 +232,16 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi
.collect(Collectors.toList());
}
@Override
public boolean addListener(DiscoveryServiceListener listener) {
return listeners.add(listener);
}
@Override
public boolean removeListener(DiscoveryServiceListener listener) {
return listeners.remove(listener);
}
@Override
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
if (stopped) {
log.debug("Ignoring application ready event. Service is stopped.");
return;
}
if (client.getState() != CuratorFrameworkState.STARTED) {
log.debug("Ignoring application ready event, ZK client is not started, ZK client state [{}]", client.getState());
return;
}
publishCurrentServer();
getOtherServers().forEach(
server -> log.info("Found active server: [{}:{}]", server.getHost(), server.getPort())
@ -185,6 +250,14 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi
@Override
public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent pathChildrenCacheEvent) throws Exception {
if (stopped) {
log.debug("Ignoring {}. Service is stopped.", pathChildrenCacheEvent);
return;
}
if (client.getState() != CuratorFrameworkState.STARTED) {
log.debug("Ignoring {}, ZK client is not started, ZK client state [{}]", pathChildrenCacheEvent, client.getState());
return;
}
ChildData data = pathChildrenCacheEvent.getData();
if (data == null) {
log.debug("Ignoring {} due to empty child data", pathChildrenCacheEvent);
@ -193,12 +266,16 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi
log.debug("Ignoring {} due to empty child's data", pathChildrenCacheEvent);
return;
} else if (nodePath != null && nodePath.equals(data.getPath())) {
if (pathChildrenCacheEvent.getType() == CHILD_REMOVED) {
log.info("ZK node for current instance is somehow deleted.");
publishCurrentServer();
}
log.debug("Ignoring event about current server {}", pathChildrenCacheEvent);
return;
}
ServerInstance instance;
try {
ServerAddress serverAddress = SerializationUtils.deserialize(data.getData());
ServerAddress serverAddress = SerializationUtils.deserialize(data.getData());
instance = new ServerInstance(serverAddress);
} catch (SerializationException e) {
log.error("Failed to decode server instance for node {}", data.getPath(), e);
@ -207,17 +284,20 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi
log.info("Processing [{}] event for [{}:{}]", pathChildrenCacheEvent.getType(), instance.getHost(), instance.getPort());
switch (pathChildrenCacheEvent.getType()) {
case CHILD_ADDED:
routingService.onServerAdded(instance);
tsSubService.onClusterUpdate();
deviceStateService.onClusterUpdate();
listeners.forEach(listener -> listener.onServerAdded(instance));
actorService.onServerAdded(instance);
break;
case CHILD_UPDATED:
listeners.forEach(listener -> listener.onServerUpdated(instance));
routingService.onServerUpdated(instance);
actorService.onServerUpdated(instance);
break;
case CHILD_REMOVED:
routingService.onServerRemoved(instance);
tsSubService.onClusterUpdate();
deviceStateService.onClusterUpdate();
listeners.forEach(listener -> listener.onServerRemoved(instance));
actorService.onServerRemoved(instance);
break;
default:
break;

9
application/src/main/java/org/thingsboard/server/service/cluster/routing/ClusterRoutingService.java

@ -17,6 +17,8 @@ package org.thingsboard.server.service.cluster.routing;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.cluster.ServerType;
import org.thingsboard.server.service.cluster.discovery.DiscoveryServiceListener;
import java.util.Optional;
import java.util.UUID;
@ -24,7 +26,7 @@ import java.util.UUID;
/**
* @author Andrew Shvayka
*/
public interface ClusterRoutingService {
public interface ClusterRoutingService extends DiscoveryServiceListener {
ServerAddress getCurrentServer();
@ -32,4 +34,9 @@ public interface ClusterRoutingService {
Optional<ServerAddress> resolveById(EntityId entityId);
Optional<ServerAddress> resolveByUuid(ServerType server, UUID uuid);
Optional<ServerAddress> resolveById(ServerType server, EntityId entityId);
}

38
application/src/main/java/org/thingsboard/server/service/cluster/routing/ConsistentClusterRoutingService.java

@ -24,12 +24,14 @@ import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.cluster.ServerType;
import org.thingsboard.server.service.cluster.discovery.DiscoveryService;
import org.thingsboard.server.service.cluster.discovery.DiscoveryServiceListener;
import org.thingsboard.server.service.cluster.discovery.ServerInstance;
import org.thingsboard.server.utils.MiscUtils;
import javax.annotation.PostConstruct;
import java.util.Arrays;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentNavigableMap;
@ -41,7 +43,7 @@ import java.util.concurrent.ConcurrentSkipListMap;
@Service
@Slf4j
public class ConsistentClusterRoutingService implements ClusterRoutingService, DiscoveryServiceListener {
public class ConsistentClusterRoutingService implements ClusterRoutingService {
@Autowired
private DiscoveryService discoveryService;
@ -55,15 +57,19 @@ public class ConsistentClusterRoutingService implements ClusterRoutingService, D
private HashFunction hashFunction;
private final ConcurrentNavigableMap<Long, ServerInstance> circle =
new ConcurrentSkipListMap<>();
private ConsistentHashCircle[] circles;
private ConsistentHashCircle rootCircle;
@PostConstruct
public void init() {
log.info("Initializing Cluster routing service!");
hashFunction = MiscUtils.forName(hashFunctionName);
discoveryService.addListener(this);
this.hashFunction = MiscUtils.forName(hashFunctionName);
this.currentServer = discoveryService.getCurrentServer();
this.circles = new ConsistentHashCircle[ServerType.values().length];
for (ServerType serverType : ServerType.values()) {
circles[serverType.ordinal()] = new ConsistentHashCircle();
}
rootCircle = circles[ServerType.CORE.ordinal()];
addNode(discoveryService.getCurrentServer());
for (ServerInstance instance : discoveryService.getOtherServers()) {
addNode(instance);
@ -79,11 +85,25 @@ public class ConsistentClusterRoutingService implements ClusterRoutingService, D
@Override
public Optional<ServerAddress> resolveById(EntityId entityId) {
return resolveByUuid(entityId.getId());
return resolveByUuid(rootCircle, entityId.getId());
}
@Override
public Optional<ServerAddress> resolveByUuid(UUID uuid) {
return resolveByUuid(rootCircle, uuid);
}
@Override
public Optional<ServerAddress> resolveByUuid(ServerType server, UUID uuid) {
return resolveByUuid(circles[server.ordinal()], uuid);
}
@Override
public Optional<ServerAddress> resolveById(ServerType server, EntityId entityId) {
return resolveByUuid(circles[server.ordinal()], entityId.getId());
}
private Optional<ServerAddress> resolveByUuid(ConsistentHashCircle circle, UUID uuid) {
Assert.notNull(uuid);
if (circle.isEmpty()) {
return Optional.empty();
@ -125,13 +145,13 @@ public class ConsistentClusterRoutingService implements ClusterRoutingService, D
private void addNode(ServerInstance instance) {
for (int i = 0; i < virtualNodesSize; i++) {
circle.put(hash(instance, i).asLong(), instance);
circles[instance.getServerAddress().getServerType().ordinal()].put(hash(instance, i).asLong(), instance);
}
}
private void removeNode(ServerInstance instance) {
for (int i = 0; i < virtualNodesSize; i++) {
circle.remove(hash(instance, i).asLong());
circles[instance.getServerAddress().getServerType().ordinal()].remove(hash(instance, i).asLong());
}
}
@ -141,7 +161,7 @@ public class ConsistentClusterRoutingService implements ClusterRoutingService, D
private void logCircle() {
log.trace("Consistent Hash Circle Start");
circle.entrySet().forEach((e) -> log.debug("{} -> {}", e.getKey(), e.getValue().getServerAddress()));
Arrays.asList(circles).forEach(ConsistentHashCircle::log);
log.trace("Consistent Hash Circle End");
}

63
application/src/main/java/org/thingsboard/server/service/cluster/routing/ConsistentHashCircle.java

@ -0,0 +1,63 @@
/**
* Copyright © 2016-2018 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.cluster.routing;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.service.cluster.discovery.ServerInstance;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
/**
* Created by ashvayka on 23.09.18.
*/
@Slf4j
public class ConsistentHashCircle {
private final ConcurrentNavigableMap<Long, ServerInstance> circle =
new ConcurrentSkipListMap<>();
public void put(long hash, ServerInstance instance) {
circle.put(hash, instance);
}
public void remove(long hash) {
circle.remove(hash);
}
public boolean isEmpty() {
return circle.isEmpty();
}
public boolean containsKey(Long hash) {
return circle.containsKey(hash);
}
public ConcurrentNavigableMap<Long, ServerInstance> tailMap(Long hash) {
return circle.tailMap(hash);
}
public Long firstKey() {
return circle.firstKey();
}
public ServerInstance get(Long hash) {
return circle.get(hash);
}
public void log() {
circle.entrySet().forEach((e) -> log.debug("{} -> {}", e.getKey(), e.getValue().getServerAddress()));
}
}

16
application/src/main/java/org/thingsboard/server/service/cluster/rpc/GrpcSession.java

@ -15,10 +15,13 @@
*/
package org.thingsboard.server.service.cluster.rpc;
import io.grpc.Channel;
import io.grpc.ManagedChannel;
import io.grpc.stub.StreamObserver;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.cluster.ServerType;
import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
import java.io.Closeable;
@ -33,6 +36,7 @@ public final class GrpcSession implements Closeable {
private final UUID sessionId;
private final boolean client;
private final GrpcSessionListener listener;
private final ManagedChannel channel;
private StreamObserver<ClusterAPIProtos.ClusterMessage> inputStream;
private StreamObserver<ClusterAPIProtos.ClusterMessage> outputStream;
@ -40,10 +44,10 @@ public final class GrpcSession implements Closeable {
private ServerAddress remoteServer;
public GrpcSession(GrpcSessionListener listener) {
this(null, listener);
this(null, listener, null);
}
public GrpcSession(ServerAddress remoteServer, GrpcSessionListener listener) {
public GrpcSession(ServerAddress remoteServer, GrpcSessionListener listener, ManagedChannel channel) {
this.sessionId = UUID.randomUUID();
this.listener = listener;
if (remoteServer != null) {
@ -53,6 +57,7 @@ public final class GrpcSession implements Closeable {
} else {
this.client = false;
}
this.channel = channel;
}
public void initInputStream() {
@ -61,8 +66,8 @@ public final class GrpcSession implements Closeable {
public void onNext(ClusterAPIProtos.ClusterMessage clusterMessage) {
if (!connected && clusterMessage.getMessageType() == ClusterAPIProtos.MessageType.CONNECT_RPC_MESSAGE) {
connected = true;
ServerAddress rpcAddress = new ServerAddress(clusterMessage.getServerAddress().getHost(), clusterMessage.getServerAddress().getPort());
remoteServer = new ServerAddress(rpcAddress.getHost(), rpcAddress.getPort());
ServerAddress rpcAddress = new ServerAddress(clusterMessage.getServerAddress().getHost(), clusterMessage.getServerAddress().getPort(), ServerType.CORE);
remoteServer = new ServerAddress(rpcAddress.getHost(), rpcAddress.getPort(), ServerType.CORE);
listener.onConnected(GrpcSession.this);
}
if (connected) {
@ -104,5 +109,8 @@ public final class GrpcSession implements Closeable {
} catch (IllegalStateException e) {
log.debug("[{}] Failed to close output stream: {}", sessionId, e.getMessage());
}
if (channel != null) {
channel.shutdownNow();
}
}
}

39
application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java

@ -30,7 +30,6 @@ import org.thingsboard.rule.engine.api.NodeConfiguration;
import org.thingsboard.rule.engine.api.NodeDefinition;
import org.thingsboard.rule.engine.api.RuleNode;
import org.thingsboard.rule.engine.api.TbRelationTypes;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.dao.component.ComponentDescriptorService;
@ -52,6 +51,7 @@ import java.util.Set;
@Slf4j
public class AnnotationComponentDiscoveryService implements ComponentDiscoveryService {
public static final int MAX_OPTIMISITC_RETRIES = 3;
@Value("${plugins.scan_packages}")
private String[] scanPackages;
@ -81,17 +81,32 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
private void registerRuleNodeComponents() {
Set<BeanDefinition> ruleNodeBeanDefinitions = getBeanDefinitions(RuleNode.class);
for (BeanDefinition def : ruleNodeBeanDefinitions) {
try {
String clazzName = def.getBeanClassName();
Class<?> clazz = Class.forName(clazzName);
RuleNode ruleNodeAnnotation = clazz.getAnnotation(RuleNode.class);
ComponentType type = ruleNodeAnnotation.type();
ComponentDescriptor component = scanAndPersistComponent(def, type);
components.put(component.getClazz(), component);
componentsMap.computeIfAbsent(type, k -> new ArrayList<>()).add(component);
} catch (Exception e) {
log.error("Can't initialize component {}, due to {}", def.getBeanClassName(), e.getMessage(), e);
throw new RuntimeException(e);
int retryCount = 0;
Exception cause = null;
while (retryCount < MAX_OPTIMISITC_RETRIES) {
try {
String clazzName = def.getBeanClassName();
Class<?> clazz = Class.forName(clazzName);
RuleNode ruleNodeAnnotation = clazz.getAnnotation(RuleNode.class);
ComponentType type = ruleNodeAnnotation.type();
ComponentDescriptor component = scanAndPersistComponent(def, type);
components.put(component.getClazz(), component);
componentsMap.computeIfAbsent(type, k -> new ArrayList<>()).add(component);
break;
} catch (Exception e) {
log.trace("Can't initialize component {}, due to {}", def.getBeanClassName(), e.getMessage(), e);
cause = e;
retryCount++;
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
throw new RuntimeException(e1);
}
}
}
if (cause != null && retryCount == MAX_OPTIMISITC_RETRIES) {
log.error("Can't initialize component {}, due to {}", def.getBeanClassName(), cause.getMessage(), cause);
throw new RuntimeException(cause);
}
}
}

19
application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseSchemaService.java → application/src/main/java/org/thingsboard/server/service/install/CassandraAbstractDatabaseSchemaService.java

@ -17,24 +17,17 @@ package org.thingsboard.server.service.install;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.cassandra.CassandraInstallCluster;
import org.thingsboard.server.dao.util.NoSqlDao;
import org.thingsboard.server.service.install.cql.CQLStatementsParser;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
@Service
@NoSqlDao
@Profile("install")
@Slf4j
public class CassandraDatabaseSchemaService implements DatabaseSchemaService {
public abstract class CassandraAbstractDatabaseSchemaService implements DatabaseSchemaService {
private static final String CASSANDRA_DIR = "cassandra";
private static final String SCHEMA_CQL = "schema.cql";
@Autowired
private CassandraInstallCluster cluster;
@ -42,10 +35,16 @@ public class CassandraDatabaseSchemaService implements DatabaseSchemaService {
@Autowired
private InstallScripts installScripts;
private final String schemaCql;
protected CassandraAbstractDatabaseSchemaService(String schemaCql) {
this.schemaCql = schemaCql;
}
@Override
public void createDatabaseSchema() throws Exception {
log.info("Installing Cassandra DataBase schema...");
Path schemaFile = Paths.get(installScripts.getDataDir(), CASSANDRA_DIR, SCHEMA_CQL);
log.info("Installing Cassandra DataBase schema part: " + schemaCql);
Path schemaFile = Paths.get(installScripts.getDataDir(), CASSANDRA_DIR, schemaCql);
loadCql(schemaFile);
}

50
application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java

@ -39,10 +39,19 @@ import static org.thingsboard.server.service.install.DatabaseHelper.CONFIGURATIO
import static org.thingsboard.server.service.install.DatabaseHelper.CUSTOMER_ID;
import static org.thingsboard.server.service.install.DatabaseHelper.DASHBOARD;
import static org.thingsboard.server.service.install.DatabaseHelper.DEVICE;
import static org.thingsboard.server.service.install.DatabaseHelper.END_TS;
import static org.thingsboard.server.service.install.DatabaseHelper.ENTITY_ID;
import static org.thingsboard.server.service.install.DatabaseHelper.ENTITY_TYPE;
import static org.thingsboard.server.service.install.DatabaseHelper.ENTITY_VIEW;
import static org.thingsboard.server.service.install.DatabaseHelper.ENTITY_VIEWS;
import static org.thingsboard.server.service.install.DatabaseHelper.ID;
import static org.thingsboard.server.service.install.DatabaseHelper.KEYS;
import static org.thingsboard.server.service.install.DatabaseHelper.NAME;
import static org.thingsboard.server.service.install.DatabaseHelper.SEARCH_TEXT;
import static org.thingsboard.server.service.install.DatabaseHelper.START_TS;
import static org.thingsboard.server.service.install.DatabaseHelper.TENANT_ID;
import static org.thingsboard.server.service.install.DatabaseHelper.TITLE;
import static org.thingsboard.server.service.install.DatabaseHelper.TYPE;
@Service
@NoSqlDao
@ -203,6 +212,47 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
log.info("Schema updated.");
break;
case "2.0.0":
log.info("Updating schema ...");
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.1.1", SCHEMA_UPDATE_CQL);
loadCql(schemaUpdateFile);
log.info("Schema updated.");
break;
case "2.1.1":
log.info("Upgrading Cassandra DataBase from version {} to 2.1.2 ...", fromVersion);
cluster.getSession();
ks = cluster.getCluster().getMetadata().getKeyspace(cluster.getKeyspaceName());
log.info("Dumping entity views ...");
Path entityViewsDump = CassandraDbHelper.dumpCfIfExists(ks, cluster.getSession(), ENTITY_VIEWS,
new String[]{ID, ENTITY_ID, ENTITY_TYPE, TENANT_ID, CUSTOMER_ID, NAME, TYPE, KEYS, START_TS, END_TS, SEARCH_TEXT, ADDITIONAL_INFO},
new String[]{"", "", "", "", "", "", "default", "", "0", "0", "", ""},
"tb-entity-views");
log.info("Entity views dumped.");
log.info("Updating schema ...");
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.1.2", SCHEMA_UPDATE_CQL);
loadCql(schemaUpdateFile);
log.info("Schema updated.");
log.info("Restoring entity views ...");
if (entityViewsDump != null) {
CassandraDbHelper.loadCf(ks, cluster.getSession(), ENTITY_VIEW,
new String[]{ID, ENTITY_ID, ENTITY_TYPE, TENANT_ID, CUSTOMER_ID, NAME, TYPE, KEYS, START_TS, END_TS, SEARCH_TEXT, ADDITIONAL_INFO}, entityViewsDump);
Files.deleteIfExists(entityViewsDump);
}
log.info("Entity views restored.");
break;
case "2.1.3":
break;
default:
throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion);
}

30
application/src/main/java/org/thingsboard/server/service/install/CassandraEntityDatabaseSchemaService.java

@ -0,0 +1,30 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.install;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.util.NoSqlDao;
@Service
@NoSqlDao
@Profile("install")
public class CassandraEntityDatabaseSchemaService extends CassandraAbstractDatabaseSchemaService
implements EntityDatabaseSchemaService {
public CassandraEntityDatabaseSchemaService() {
super("schema-entities.cql");
}
}

30
application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseSchemaService.java

@ -0,0 +1,30 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.install;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.util.NoSqlTsDao;
@Service
@NoSqlTsDao
@Profile("install")
public class CassandraTsDatabaseSchemaService extends CassandraAbstractDatabaseSchemaService
implements TsDatabaseSchemaService {
public CassandraTsDatabaseSchemaService() {
super("schema-ts.cql");
}
}

9
application/src/main/java/org/thingsboard/server/service/install/DatabaseHelper.java

@ -45,14 +45,23 @@ public class DatabaseHelper {
public static final CSVFormat CSV_DUMP_FORMAT = CSVFormat.DEFAULT.withNullString("\\N");
public static final String DEVICE = "device";
public static final String ENTITY_ID = "entity_id";
public static final String TENANT_ID = "tenant_id";
public static final String ENTITY_TYPE = "entity_type";
public static final String CUSTOMER_ID = "customer_id";
public static final String SEARCH_TEXT = "search_text";
public static final String ADDITIONAL_INFO = "additional_info";
public static final String ASSET = "asset";
public static final String DASHBOARD = "dashboard";
public static final String ENTITY_VIEWS = "entity_views";
public static final String ENTITY_VIEW = "entity_view";
public static final String ID = "id";
public static final String TITLE = "title";
public static final String TYPE = "type";
public static final String NAME = "name";
public static final String KEYS = "keys";
public static final String START_TS = "start_ts";
public static final String END_TS = "end_ts";
public static final String ASSIGNED_CUSTOMERS = "assigned_customers";
public static final String CONFIGURATION = "configuration";

7
common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionActorToAdaptorMsg.java → application/src/main/java/org/thingsboard/server/service/install/EntityDatabaseSchemaService.java

@ -13,10 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.msg.session;
public interface SessionActorToAdaptorMsg extends SessionMsg {
ToDeviceMsg getMsg();
package org.thingsboard.server.service.install;
public interface EntityDatabaseSchemaService extends DatabaseSchemaService {
}

19
application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseSchemaService.java → application/src/main/java/org/thingsboard/server/service/install/SqlAbstractDatabaseSchemaService.java

@ -18,9 +18,6 @@ package org.thingsboard.server.service.install;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.util.SqlDao;
import java.nio.charset.Charset;
import java.nio.file.Files;
@ -29,14 +26,10 @@ import java.nio.file.Paths;
import java.sql.Connection;
import java.sql.DriverManager;
@Service
@Profile("install")
@Slf4j
@SqlDao
public class SqlDatabaseSchemaService implements DatabaseSchemaService {
public abstract class SqlAbstractDatabaseSchemaService implements DatabaseSchemaService {
private static final String SQL_DIR = "sql";
private static final String SCHEMA_SQL = "schema.sql";
@Value("${spring.datasource.url}")
private String dbUrl;
@ -50,12 +43,18 @@ public class SqlDatabaseSchemaService implements DatabaseSchemaService {
@Autowired
private InstallScripts installScripts;
private final String schemaSql;
protected SqlAbstractDatabaseSchemaService(String schemaSql) {
this.schemaSql = schemaSql;
}
@Override
public void createDatabaseSchema() throws Exception {
log.info("Installing SQL DataBase schema...");
log.info("Installing SQL DataBase schema part: " + schemaSql);
Path schemaFile = Paths.get(installScripts.getDataDir(), SQL_DIR, SCHEMA_SQL);
Path schemaFile = Paths.get(installScripts.getDataDir(), SQL_DIR, schemaSql);
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
String sql = new String(Files.readAllBytes(schemaFile), Charset.forName("UTF-8"));
conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to load initial thingsboard database schema

50
application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java

@ -31,14 +31,24 @@ import java.nio.file.Paths;
import java.sql.Connection;
import java.sql.DriverManager;
import static org.thingsboard.server.service.install.DatabaseHelper.ADDITIONAL_INFO;
import static org.thingsboard.server.service.install.DatabaseHelper.ASSIGNED_CUSTOMERS;
import static org.thingsboard.server.service.install.DatabaseHelper.CONFIGURATION;
import static org.thingsboard.server.service.install.DatabaseHelper.CUSTOMER_ID;
import static org.thingsboard.server.service.install.DatabaseHelper.DASHBOARD;
import static org.thingsboard.server.service.install.DatabaseHelper.END_TS;
import static org.thingsboard.server.service.install.DatabaseHelper.ENTITY_ID;
import static org.thingsboard.server.service.install.DatabaseHelper.ENTITY_TYPE;
import static org.thingsboard.server.service.install.DatabaseHelper.ENTITY_VIEW;
import static org.thingsboard.server.service.install.DatabaseHelper.ENTITY_VIEWS;
import static org.thingsboard.server.service.install.DatabaseHelper.ID;
import static org.thingsboard.server.service.install.DatabaseHelper.KEYS;
import static org.thingsboard.server.service.install.DatabaseHelper.NAME;
import static org.thingsboard.server.service.install.DatabaseHelper.SEARCH_TEXT;
import static org.thingsboard.server.service.install.DatabaseHelper.START_TS;
import static org.thingsboard.server.service.install.DatabaseHelper.TENANT_ID;
import static org.thingsboard.server.service.install.DatabaseHelper.TITLE;
import static org.thingsboard.server.service.install.DatabaseHelper.TYPE;
@Service
@Profile("install")
@ -107,6 +117,46 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
log.info("Schema updated.");
}
break;
case "2.0.0":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Updating schema ...");
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.1.1", SCHEMA_UPDATE_SQL);
loadSql(schemaUpdateFile, conn);
log.info("Schema updated.");
}
break;
case "2.1.1":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Dumping entity views ...");
Path entityViewsDump = SqlDbHelper.dumpTableIfExists(conn, ENTITY_VIEWS,
new String[]{ID, ENTITY_ID, ENTITY_TYPE, TENANT_ID, CUSTOMER_ID, TYPE, NAME, KEYS, START_TS, END_TS, SEARCH_TEXT, ADDITIONAL_INFO},
new String[]{"", "", "", "", "", "default", "", "", "0", "0", "", ""},
"tb-entity-views", true);
log.info("Entity views dumped.");
log.info("Updating schema ...");
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.1.2", SCHEMA_UPDATE_SQL);
loadSql(schemaUpdateFile, conn);
log.info("Schema updated.");
log.info("Restoring entity views ...");
if (entityViewsDump != null) {
SqlDbHelper.loadTable(conn, ENTITY_VIEW,
new String[]{ID, ENTITY_ID, ENTITY_TYPE, TENANT_ID, CUSTOMER_ID, TYPE, NAME, KEYS, START_TS, END_TS, SEARCH_TEXT, ADDITIONAL_INFO}, entityViewsDump, true);
Files.deleteIfExists(entityViewsDump);
}
log.info("Entity views restored.");
}
break;
case "2.1.3":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Updating schema ...");
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.2.0", SCHEMA_UPDATE_SQL);
loadSql(schemaUpdateFile, conn);
log.info("Schema updated.");
}
break;
default:
throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
}

30
application/src/main/java/org/thingsboard/server/service/install/SqlEntityDatabaseSchemaService.java

@ -0,0 +1,30 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.install;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.util.SqlDao;
@Service
@SqlDao
@Profile("install")
public class SqlEntityDatabaseSchemaService extends SqlAbstractDatabaseSchemaService
implements EntityDatabaseSchemaService {
public SqlEntityDatabaseSchemaService() {
super("schema-entities.sql");
}
}

20
application/src/main/java/org/thingsboard/server/actors/session/SessionTerminationMsg.java → application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseSchemaService.java

@ -13,14 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors.session;
package org.thingsboard.server.service.install;
import org.thingsboard.server.actors.shared.ActorTerminationMsg;
import org.thingsboard.server.common.data.id.SessionId;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.util.SqlTsDao;
public class SessionTerminationMsg extends ActorTerminationMsg<SessionId> {
public SessionTerminationMsg(SessionId id) {
super(id);
@Service
@SqlTsDao
@Profile("install")
public class SqlTsDatabaseSchemaService extends SqlAbstractDatabaseSchemaService
implements TsDatabaseSchemaService {
public SqlTsDatabaseSchemaService() {
super("schema-ts.sql");
}
}
}

5
common/message/src/main/java/org/thingsboard/server/common/msg/core/StatusCodeResponse.java → application/src/main/java/org/thingsboard/server/service/install/TsDatabaseSchemaService.java

@ -13,8 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.msg.core;
public interface StatusCodeResponse extends ResponseMsg<Integer>{
package org.thingsboard.server.service.install;
public interface TsDatabaseSchemaService extends DatabaseSchemaService {
}

4
application/src/main/java/org/thingsboard/server/service/install/cql/CassandraDbHelper.java

@ -147,6 +147,8 @@ public class CassandraDbHelper {
str = new Double(row.getDouble(index)).toString();
} else if (type == DataType.cint()) {
str = new Integer(row.getInt(index)).toString();
} else if (type == DataType.bigint()) {
str = new Long(row.getLong(index)).toString();
} else if (type == DataType.uuid()) {
str = row.getUUID(index).toString();
} else if (type == DataType.timeuuid()) {
@ -193,6 +195,8 @@ public class CassandraDbHelper {
boundStatement.setDouble(column, Double.valueOf(value));
} else if (type == DataType.cint()) {
boundStatement.setInt(column, Integer.valueOf(value));
} else if (type == DataType.bigint()) {
boundStatement.setLong(column, Long.valueOf(value));
} else if (type == DataType.uuid()) {
boundStatement.setUUID(column, UUID.fromString(value));
} else if (type == DataType.timeuuid()) {

111
application/src/main/java/org/thingsboard/server/service/queue/DefaultMsgQueueService.java

@ -1,111 +0,0 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.queue;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.transport.quota.tenant.TenantQuotaService;
import org.thingsboard.server.dao.queue.MsgQueue;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
@Service
@Slf4j
public class DefaultMsgQueueService implements MsgQueueService {
@Value("${actors.rule.queue.max_size}")
private long queueMaxSize;
@Value("${actors.rule.queue.cleanup_period}")
private long queueCleanUpPeriod;
@Autowired
private MsgQueue msgQueue;
@Autowired
private TenantQuotaService quotaService;
private ScheduledExecutorService cleanupExecutor;
private Map<TenantId, AtomicLong> pendingCountPerTenant = new ConcurrentHashMap<>();
@PostConstruct
public void init() {
if (queueCleanUpPeriod > 0) {
cleanupExecutor = Executors.newSingleThreadScheduledExecutor();
cleanupExecutor.scheduleAtFixedRate(() -> cleanup(),
queueCleanUpPeriod, queueCleanUpPeriod, TimeUnit.SECONDS);
}
}
@PreDestroy
public void stop() {
if (cleanupExecutor != null) {
cleanupExecutor.shutdownNow();
}
}
@Override
public ListenableFuture<Void> put(TenantId tenantId, TbMsg msg, UUID nodeId, long clusterPartition) {
if(quotaService.isQuotaExceeded(tenantId.getId().toString())) {
log.warn("Tenant TbMsg Quota exceeded for [{}:{}] . Reject", tenantId.getId());
return Futures.immediateFailedFuture(new RuntimeException("Tenant TbMsg Quota exceeded"));
}
AtomicLong pendingMsgCount = pendingCountPerTenant.computeIfAbsent(tenantId, key -> new AtomicLong());
if (pendingMsgCount.incrementAndGet() < queueMaxSize) {
return msgQueue.put(tenantId, msg, nodeId, clusterPartition);
} else {
pendingMsgCount.decrementAndGet();
return Futures.immediateFailedFuture(new RuntimeException("Message queue is full!"));
}
}
@Override
public ListenableFuture<Void> ack(TenantId tenantId, TbMsg msg, UUID nodeId, long clusterPartition) {
ListenableFuture<Void> result = msgQueue.ack(tenantId, msg, nodeId, clusterPartition);
AtomicLong pendingMsgCount = pendingCountPerTenant.computeIfAbsent(tenantId, key -> new AtomicLong());
pendingMsgCount.decrementAndGet();
return result;
}
@Override
public Iterable<TbMsg> findUnprocessed(TenantId tenantId, UUID nodeId, long clusterPartition) {
return msgQueue.findUnprocessed(tenantId, nodeId, clusterPartition);
}
private void cleanup() {
pendingCountPerTenant.forEach((tenantId, pendingMsgCount) -> {
pendingMsgCount.set(0);
msgQueue.cleanUp(tenantId);
});
}
}

32
application/src/main/java/org/thingsboard/server/service/queue/MsgQueueService.java

@ -1,32 +0,0 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.queue;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.TbMsg;
import java.util.UUID;
public interface MsgQueueService {
ListenableFuture<Void> put(TenantId tenantId, TbMsg msg, UUID nodeId, long clusterPartition);
ListenableFuture<Void> ack(TenantId tenantId, TbMsg msg, UUID nodeId, long clusterPartition);
Iterable<TbMsg> findUnprocessed(TenantId tenantId, UUID nodeId, long clusterPartition);
}

68
application/src/main/java/org/thingsboard/server/service/rpc/DefaultDeviceRpcService.java

@ -90,7 +90,7 @@ public class DefaultDeviceRpcService implements DeviceRpcService {
@Override
public void processRestAPIRpcRequestToRuleEngine(ToDeviceRpcRequest request, Consumer<FromDeviceRpcResponse> responseConsumer) {
log.trace("[{}] Processing local rpc call to rule engine [{}]", request.getTenantId(), request.getDeviceId());
log.trace("[{}][{}] Processing REST API call to rule engine [{}]", request.getTenantId(), request.getId(), request.getDeviceId());
UUID requestId = request.getId();
localToRuleEngineRpcRequests.put(requestId, responseConsumer);
sendRpcRequestToRuleEngine(request);
@ -98,31 +98,11 @@ public class DefaultDeviceRpcService implements DeviceRpcService {
}
@Override
public void processRestAPIRpcResponseFromRuleEngine(FromDeviceRpcResponse response) {
UUID requestId = response.getId();
Consumer<FromDeviceRpcResponse> consumer = localToRuleEngineRpcRequests.remove(requestId);
if (consumer != null) {
consumer.accept(response);
} else {
log.trace("[{}] Unknown or stale rpc response received [{}]", requestId, response);
}
}
@Override
public void processRpcRequestToDevice(ToDeviceRpcRequest request, Consumer<FromDeviceRpcResponse> responseConsumer) {
log.trace("[{}] Processing local rpc call to device [{}]", request.getTenantId(), request.getDeviceId());
UUID requestId = request.getId();
localToDeviceRpcRequests.put(requestId, responseConsumer);
sendRpcRequestToDevice(request);
scheduleTimeout(request, requestId, localToDeviceRpcRequests);
}
@Override
public void processRpcResponseFromDevice(FromDeviceRpcResponse response) {
log.trace("[{}] response to request: [{}]", this.hashCode(), response.getId());
if (routingService.getCurrentServer().equals(response.getServerAddress())) {
public void processResponseToServerSideRPCRequestFromRuleEngine(ServerAddress requestOriginAddress, FromDeviceRpcResponse response) {
log.trace("[{}] Received response to server-side RPC request from rule engine: [{}]", response.getId(), requestOriginAddress);
if (routingService.getCurrentServer().equals(requestOriginAddress)) {
UUID requestId = response.getId();
Consumer<FromDeviceRpcResponse> consumer = localToDeviceRpcRequests.remove(requestId);
Consumer<FromDeviceRpcResponse> consumer = localToRuleEngineRpcRequests.remove(requestId);
if (consumer != null) {
consumer.accept(response);
} else {
@ -138,12 +118,33 @@ public class DefaultDeviceRpcService implements DeviceRpcService {
} else {
builder.setError(-1);
}
rpcService.tell(response.getServerAddress(), ClusterAPIProtos.MessageType.CLUSTER_RPC_FROM_DEVICE_RESPONSE_MESSAGE, builder.build().toByteArray());
rpcService.tell(requestOriginAddress, ClusterAPIProtos.MessageType.CLUSTER_RPC_FROM_DEVICE_RESPONSE_MESSAGE, builder.build().toByteArray());
}
}
@Override
public void forwardServerSideRPCRequestToDeviceActor(ToDeviceRpcRequest request, Consumer<FromDeviceRpcResponse> responseConsumer) {
log.trace("[{}][{}] Processing local rpc call to device actor [{}]", request.getTenantId(), request.getId(), request.getDeviceId());
UUID requestId = request.getId();
localToDeviceRpcRequests.put(requestId, responseConsumer);
sendRpcRequestToDevice(request);
scheduleTimeout(request, requestId, localToDeviceRpcRequests);
}
@Override
public void processResponseToServerSideRPCRequestFromDeviceActor(FromDeviceRpcResponse response) {
log.trace("[{}] Received response to server-side RPC request from device actor.", response.getId());
UUID requestId = response.getId();
Consumer<FromDeviceRpcResponse> consumer = localToDeviceRpcRequests.remove(requestId);
if (consumer != null) {
consumer.accept(response);
} else {
log.trace("[{}] Unknown or stale rpc response received [{}]", requestId, response);
}
}
@Override
public void processRemoteResponseFromDevice(ServerAddress serverAddress, byte[] data) {
public void processResponseToServerSideRPCRequestFromRemoteServer(ServerAddress serverAddress, byte[] data) {
ClusterAPIProtos.FromDeviceRPCResponseProto proto;
try {
proto = ClusterAPIProtos.FromDeviceRPCResponseProto.parseFrom(data);
@ -151,13 +152,12 @@ public class DefaultDeviceRpcService implements DeviceRpcService {
throw new RuntimeException(e);
}
RpcError error = proto.getError() > 0 ? RpcError.values()[proto.getError()] : null;
FromDeviceRpcResponse response = new FromDeviceRpcResponse(new UUID(proto.getRequestIdMSB(), proto.getRequestIdLSB()), routingService.getCurrentServer(),
proto.getResponse(), error);
processRpcResponseFromDevice(response);
FromDeviceRpcResponse response = new FromDeviceRpcResponse(new UUID(proto.getRequestIdMSB(), proto.getRequestIdLSB()), proto.getResponse(), error);
processResponseToServerSideRPCRequestFromRuleEngine(routingService.getCurrentServer(), response);
}
@Override
public void sendRpcReplyToDevice(TenantId tenantId, DeviceId deviceId, int requestId, String body) {
public void sendReplyToRpcCallFromDevice(TenantId tenantId, DeviceId deviceId, int requestId, String body) {
ToServerRpcResponseActorMsg rpcMsg = new ToServerRpcResponseActorMsg(tenantId, deviceId, new ToServerRpcResponseMsg(requestId, body));
forward(deviceId, rpcMsg);
}
@ -166,6 +166,8 @@ public class DefaultDeviceRpcService implements DeviceRpcService {
ObjectNode entityNode = json.createObjectNode();
TbMsgMetaData metaData = new TbMsgMetaData();
metaData.putValue("requestUUID", msg.getId().toString());
metaData.putValue("originHost", routingService.getCurrentServer().getHost());
metaData.putValue("originPort", Integer.toString(routingService.getCurrentServer().getPort()));
metaData.putValue("expirationTime", Long.toString(msg.getExpirationTime()));
metaData.putValue("oneway", Boolean.toString(msg.isOneway()));
@ -176,7 +178,7 @@ public class DefaultDeviceRpcService implements DeviceRpcService {
TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), DataConstants.RPC_CALL_FROM_SERVER_TO_DEVICE, msg.getDeviceId(), metaData, TbMsgDataType.JSON
, json.writeValueAsString(entityNode)
, null, null, 0L);
actorService.onMsg(new ServiceToRuleEngineMsg(msg.getTenantId(), tbMsg));
actorService.onMsg(new SendToClusterMsg(msg.getDeviceId(), new ServiceToRuleEngineMsg(msg.getTenantId(), tbMsg)));
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
@ -199,7 +201,7 @@ public class DefaultDeviceRpcService implements DeviceRpcService {
log.trace("[{}] timeout the request: [{}]", this.hashCode(), requestId);
Consumer<FromDeviceRpcResponse> consumer = requestsMap.remove(requestId);
if (consumer != null) {
consumer.accept(new FromDeviceRpcResponse(requestId, null, null, RpcError.TIMEOUT));
consumer.accept(new FromDeviceRpcResponse(requestId, null, RpcError.TIMEOUT));
}
}, timeout, TimeUnit.MILLISECONDS);
}

10
application/src/main/java/org/thingsboard/server/service/rpc/DeviceRpcService.java

@ -29,13 +29,13 @@ public interface DeviceRpcService {
void processRestAPIRpcRequestToRuleEngine(ToDeviceRpcRequest request, Consumer<FromDeviceRpcResponse> responseConsumer);
void processRestAPIRpcResponseFromRuleEngine(FromDeviceRpcResponse response);
void processResponseToServerSideRPCRequestFromRuleEngine(ServerAddress requestOriginAddress, FromDeviceRpcResponse response);
void processRpcRequestToDevice(ToDeviceRpcRequest request, Consumer<FromDeviceRpcResponse> responseConsumer);
void forwardServerSideRPCRequestToDeviceActor(ToDeviceRpcRequest request, Consumer<FromDeviceRpcResponse> responseConsumer);
void processRpcResponseFromDevice(FromDeviceRpcResponse response);
void processResponseToServerSideRPCRequestFromDeviceActor(FromDeviceRpcResponse response);
void sendRpcReplyToDevice(TenantId tenantId, DeviceId deviceId, int requestId, String body);
void processResponseToServerSideRPCRequestFromRemoteServer(ServerAddress serverAddress, byte[] data);
void processRemoteResponseFromDevice(ServerAddress serverAddress, byte[] bytes);
void sendReplyToRpcCallFromDevice(TenantId tenantId, DeviceId deviceId, int requestId, String body);
}

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

@ -32,8 +32,6 @@ import java.util.UUID;
public class FromDeviceRpcResponse {
@Getter
private final UUID id;
@Getter
private final ServerAddress serverAddress;
private final String response;
private final RpcError error;

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

@ -22,11 +22,8 @@ import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg;
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.cluster.ServerAddress;
import org.thingsboard.server.common.msg.core.ToServerRpcResponseMsg;
import java.util.Optional;
/**
* Created by ashvayka on 16.04.18.
*/

102
application/src/main/java/org/thingsboard/server/service/script/AbstractJsInvokeService.java

@ -0,0 +1,102 @@
/**
* Copyright © 2016-2018 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.script;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Created by ashvayka on 26.09.18.
*/
@Slf4j
public abstract class AbstractJsInvokeService implements JsInvokeService {
protected Map<UUID, String> scriptIdToNameMap = new ConcurrentHashMap<>();
protected Map<UUID, AtomicInteger> blackListedFunctions = new ConcurrentHashMap<>();
@Override
public ListenableFuture<UUID> eval(JsScriptType scriptType, String scriptBody, String... argNames) {
UUID scriptId = UUID.randomUUID();
String functionName = "invokeInternal_" + scriptId.toString().replace('-', '_');
String jsScript = generateJsScript(scriptType, functionName, scriptBody, argNames);
return doEval(scriptId, functionName, jsScript);
}
@Override
public ListenableFuture<Object> invokeFunction(UUID scriptId, Object... args) {
String functionName = scriptIdToNameMap.get(scriptId);
if (functionName == null) {
return Futures.immediateFailedFuture(new RuntimeException("No compiled script found for scriptId: [" + scriptId + "]!"));
}
if (!isBlackListed(scriptId)) {
return doInvokeFunction(scriptId, functionName, args);
} else {
return Futures.immediateFailedFuture(
new RuntimeException("Script is blacklisted due to maximum error count " + getMaxErrors() + "!"));
}
}
@Override
public ListenableFuture<Void> release(UUID scriptId) {
String functionName = scriptIdToNameMap.get(scriptId);
if (functionName != null) {
try {
scriptIdToNameMap.remove(scriptId);
blackListedFunctions.remove(scriptId);
doRelease(scriptId, functionName);
} catch (Exception e) {
return Futures.immediateFailedFuture(e);
}
}
return Futures.immediateFuture(null);
}
protected abstract ListenableFuture<UUID> doEval(UUID scriptId, String functionName, String scriptBody);
protected abstract ListenableFuture<Object> doInvokeFunction(UUID scriptId, String functionName, Object[] args);
protected abstract void doRelease(UUID scriptId, String functionName) throws Exception;
protected abstract int getMaxErrors();
protected void onScriptExecutionError(UUID scriptId) {
blackListedFunctions.computeIfAbsent(scriptId, key -> new AtomicInteger(0)).incrementAndGet();
}
private String generateJsScript(JsScriptType scriptType, String functionName, String scriptBody, String... argNames) {
switch (scriptType) {
case RULE_NODE_SCRIPT:
return RuleNodeScriptFactory.generateRuleNodeScript(functionName, scriptBody, argNames);
default:
throw new RuntimeException("No script factory implemented for scriptType: " + scriptType);
}
}
private boolean isBlackListed(UUID scriptId) {
if (blackListedFunctions.containsKey(scriptId)) {
AtomicInteger errorCount = blackListedFunctions.get(scriptId);
return errorCount.get() >= getMaxErrors();
} else {
return false;
}
}
}

109
application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java

@ -0,0 +1,109 @@
/**
* Copyright © 2016-2018 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.script;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import delight.nashornsandbox.NashornSandbox;
import delight.nashornsandbox.NashornSandboxes;
import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptException;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Slf4j
public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeService {
private NashornSandbox sandbox;
private ScriptEngine engine;
private ExecutorService monitorExecutorService;
@PostConstruct
public void init() {
if (useJsSandbox()) {
sandbox = NashornSandboxes.create();
monitorExecutorService = Executors.newFixedThreadPool(getMonitorThreadPoolSize());
sandbox.setExecutor(monitorExecutorService);
sandbox.setMaxCPUTime(getMaxCpuTime());
sandbox.allowNoBraces(false);
sandbox.setMaxPreparedStatements(30);
} else {
NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
engine = factory.getScriptEngine(new String[]{"--no-java"});
}
}
@PreDestroy
public void stop() {
if (monitorExecutorService != null) {
monitorExecutorService.shutdownNow();
}
}
protected abstract boolean useJsSandbox();
protected abstract int getMonitorThreadPoolSize();
protected abstract long getMaxCpuTime();
@Override
protected ListenableFuture<UUID> doEval(UUID scriptId, String functionName, String jsScript) {
try {
if (useJsSandbox()) {
sandbox.eval(jsScript);
} else {
engine.eval(jsScript);
}
scriptIdToNameMap.put(scriptId, functionName);
} catch (Exception e) {
log.warn("Failed to compile JS script: {}", e.getMessage(), e);
return Futures.immediateFailedFuture(e);
}
return Futures.immediateFuture(scriptId);
}
@Override
protected ListenableFuture<Object> doInvokeFunction(UUID scriptId, String functionName, Object[] args) {
try {
Object result;
if (useJsSandbox()) {
result = sandbox.getSandboxedInvocable().invokeFunction(functionName, args);
} else {
result = ((Invocable) engine).invokeFunction(functionName, args);
}
return Futures.immediateFuture(result);
} catch (Exception e) {
onScriptExecutionError(scriptId);
return Futures.immediateFailedFuture(e);
}
}
protected void doRelease(UUID scriptId, String functionName) throws ScriptException {
if (useJsSandbox()) {
sandbox.eval(functionName + " = undefined;");
} else {
engine.eval(functionName + " = undefined;");
}
}
}

188
application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsSandboxService.java

@ -1,188 +0,0 @@
/**
* Copyright © 2016-2018 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.script;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import delight.nashornsandbox.NashornSandbox;
import delight.nashornsandbox.NashornSandboxes;
import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptException;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
public abstract class AbstractNashornJsSandboxService implements JsSandboxService {
private NashornSandbox sandbox;
private ScriptEngine engine;
private ExecutorService monitorExecutorService;
private final Map<UUID, String> functionsMap = new ConcurrentHashMap<>();
private final Map<UUID,AtomicInteger> blackListedFunctions = new ConcurrentHashMap<>();
private final Map<String, Pair<UUID, AtomicInteger>> scriptToId = new ConcurrentHashMap<>();
private final Map<UUID, AtomicInteger> scriptIdToCount = new ConcurrentHashMap<>();
@PostConstruct
public void init() {
if (useJsSandbox()) {
sandbox = NashornSandboxes.create();
monitorExecutorService = Executors.newFixedThreadPool(getMonitorThreadPoolSize());
sandbox.setExecutor(monitorExecutorService);
sandbox.setMaxCPUTime(getMaxCpuTime());
sandbox.allowNoBraces(false);
sandbox.setMaxPreparedStatements(30);
} else {
NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
engine = factory.getScriptEngine(new String[]{"--no-java"});
}
}
@PreDestroy
public void stop() {
if (monitorExecutorService != null) {
monitorExecutorService.shutdownNow();
}
}
protected abstract boolean useJsSandbox();
protected abstract int getMonitorThreadPoolSize();
protected abstract long getMaxCpuTime();
protected abstract int getMaxErrors();
@Override
public ListenableFuture<UUID> eval(JsScriptType scriptType, String scriptBody, String... argNames) {
Pair<UUID, AtomicInteger> deduplicated = deduplicate(scriptType, scriptBody);
UUID scriptId = deduplicated.getLeft();
AtomicInteger duplicateCount = deduplicated.getRight();
if(duplicateCount.compareAndSet(0, 1)) {
String functionName = "invokeInternal_" + scriptId.toString().replace('-', '_');
String jsScript = generateJsScript(scriptType, functionName, scriptBody, argNames);
try {
if (useJsSandbox()) {
sandbox.eval(jsScript);
} else {
engine.eval(jsScript);
}
functionsMap.put(scriptId, functionName);
} catch (Exception e) {
duplicateCount.decrementAndGet();
log.warn("Failed to compile JS script: {}", e.getMessage(), e);
return Futures.immediateFailedFuture(e);
}
} else {
duplicateCount.incrementAndGet();
}
return Futures.immediateFuture(scriptId);
}
@Override
public ListenableFuture<Object> invokeFunction(UUID scriptId, Object... args) {
String functionName = functionsMap.get(scriptId);
if (functionName == null) {
return Futures.immediateFailedFuture(new RuntimeException("No compiled script found for scriptId: [" + scriptId + "]!"));
}
if (!isBlackListed(scriptId)) {
try {
Object result;
if (useJsSandbox()) {
result = sandbox.getSandboxedInvocable().invokeFunction(functionName, args);
} else {
result = ((Invocable)engine).invokeFunction(functionName, args);
}
return Futures.immediateFuture(result);
} catch (Exception e) {
blackListedFunctions.computeIfAbsent(scriptId, key -> new AtomicInteger(0)).incrementAndGet();
return Futures.immediateFailedFuture(e);
}
} else {
return Futures.immediateFailedFuture(
new RuntimeException("Script is blacklisted due to maximum error count " + getMaxErrors() + "!"));
}
}
@Override
public ListenableFuture<Void> release(UUID scriptId) {
AtomicInteger count = scriptIdToCount.get(scriptId);
if(count != null) {
if(count.decrementAndGet() > 0) {
return Futures.immediateFuture(null);
}
}
String functionName = functionsMap.get(scriptId);
if (functionName != null) {
try {
if (useJsSandbox()) {
sandbox.eval(functionName + " = undefined;");
} else {
engine.eval(functionName + " = undefined;");
}
functionsMap.remove(scriptId);
blackListedFunctions.remove(scriptId);
} catch (ScriptException e) {
return Futures.immediateFailedFuture(e);
}
}
return Futures.immediateFuture(null);
}
private boolean isBlackListed(UUID scriptId) {
if (blackListedFunctions.containsKey(scriptId)) {
AtomicInteger errorCount = blackListedFunctions.get(scriptId);
return errorCount.get() >= getMaxErrors();
} else {
return false;
}
}
private String generateJsScript(JsScriptType scriptType, String functionName, String scriptBody, String... argNames) {
switch (scriptType) {
case RULE_NODE_SCRIPT:
return RuleNodeScriptFactory.generateRuleNodeScript(functionName, scriptBody, argNames);
default:
throw new RuntimeException("No script factory implemented for scriptType: " + scriptType);
}
}
private Pair<UUID, AtomicInteger> deduplicate(JsScriptType scriptType, String scriptBody) {
Pair<UUID, AtomicInteger> precomputed = Pair.of(UUID.randomUUID(), new AtomicInteger());
Pair<UUID, AtomicInteger> pair = scriptToId.computeIfAbsent(deduplicateKey(scriptType, scriptBody), i -> precomputed);
AtomicInteger duplicateCount = scriptIdToCount.computeIfAbsent(pair.getLeft(), i -> pair.getRight());
return Pair.of(pair.getLeft(), duplicateCount);
}
private String deduplicateKey(JsScriptType scriptType, String scriptBody) {
return scriptType + "_" + scriptBody;
}
}

13
common/message/src/main/java/org/thingsboard/server/common/msg/session/FromDeviceMsg.java → application/src/main/java/org/thingsboard/server/service/script/JsInvokeRequest.java

@ -13,12 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.msg.session;
package org.thingsboard.server.service.script;
import java.io.Serializable;
import java.util.List;
public interface FromDeviceMsg extends Serializable {
/**
* Created by ashvayka on 25.09.18.
*/
public class JsInvokeRequest {
SessionMsgType getMsgType();
private String scriptId;
private String scriptBody;
private List<String> args;
}

29
application/src/main/java/org/thingsboard/server/service/script/JsInvokeResponse.java

@ -0,0 +1,29 @@
/**
* Copyright © 2016-2018 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.script;
import java.util.List;
/**
* Created by ashvayka on 25.09.18.
*/
public class JsInvokeResponse {
private String scriptId;
private String scriptBody;
private List<String> args;
}

3
application/src/main/java/org/thingsboard/server/service/script/JsSandboxService.java → application/src/main/java/org/thingsboard/server/service/script/JsInvokeService.java

@ -17,10 +17,11 @@
package org.thingsboard.server.service.script;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.id.EntityId;
import java.util.UUID;
public interface JsSandboxService {
public interface JsInvokeService {
ListenableFuture<UUID> eval(JsScriptType scriptType, String scriptBody, String... argNames);

12
application/src/main/java/org/thingsboard/server/service/script/NashornJsSandboxService.java → application/src/main/java/org/thingsboard/server/service/script/NashornJsInvokeService.java

@ -18,22 +18,24 @@ package org.thingsboard.server.service.script;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
@Slf4j
@ConditionalOnProperty(prefix = "js", value = "evaluator", havingValue = "local", matchIfMissing = true)
@Service
public class NashornJsSandboxService extends AbstractNashornJsSandboxService {
public class NashornJsInvokeService extends AbstractNashornJsInvokeService {
@Value("${actors.rule.js_sandbox.use_js_sandbox}")
@Value("${js.local.use_js_sandbox}")
private boolean useJsSandbox;
@Value("${actors.rule.js_sandbox.monitor_thread_pool_size}")
@Value("${js.local.monitor_thread_pool_size}")
private int monitorThreadPoolSize;
@Value("${actors.rule.js_sandbox.max_cpu_time}")
@Value("${js.local.max_cpu_time}")
private long maxCpuTime;
@Value("${actors.rule.js_sandbox.max_errors}")
@Value("${js.local.max_errors}")
private int maxErrors;
@Override

211
application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java

@ -0,0 +1,211 @@
/**
* Copyright © 2016-2018 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.script;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import org.thingsboard.server.gen.js.JsInvokeProtos;
import org.thingsboard.server.kafka.TBKafkaConsumerTemplate;
import org.thingsboard.server.kafka.TBKafkaProducerTemplate;
import org.thingsboard.server.kafka.TbKafkaRequestTemplate;
import org.thingsboard.server.kafka.TbKafkaSettings;
import org.thingsboard.server.kafka.TbNodeIdProvider;
import org.thingsboard.server.service.cluster.discovery.DiscoveryService;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@ConditionalOnProperty(prefix = "js", value = "evaluator", havingValue = "remote", matchIfMissing = true)
@Service
public class RemoteJsInvokeService extends AbstractJsInvokeService {
@Autowired
private TbNodeIdProvider nodeIdProvider;
@Autowired
private TbKafkaSettings kafkaSettings;
@Value("${js.remote.request_topic}")
private String requestTopic;
@Value("${js.remote.response_topic_prefix}")
private String responseTopicPrefix;
@Value("${js.remote.max_pending_requests}")
private long maxPendingRequests;
@Value("${js.remote.max_requests_timeout}")
private long maxRequestsTimeout;
@Value("${js.remote.response_poll_interval}")
private int responsePollDuration;
@Value("${js.remote.response_auto_commit_interval}")
private int autoCommitInterval;
@Getter
@Value("${js.remote.max_errors}")
private int maxErrors;
private TbKafkaRequestTemplate<JsInvokeProtos.RemoteJsRequest, JsInvokeProtos.RemoteJsResponse> kafkaTemplate;
private Map<UUID, String> scriptIdToBodysMap = new ConcurrentHashMap<>();
@PostConstruct
public void init() {
TBKafkaProducerTemplate.TBKafkaProducerTemplateBuilder<JsInvokeProtos.RemoteJsRequest> requestBuilder = TBKafkaProducerTemplate.builder();
requestBuilder.settings(kafkaSettings);
requestBuilder.defaultTopic(requestTopic);
requestBuilder.encoder(new RemoteJsRequestEncoder());
requestBuilder.enricher((request, responseTopic, requestId) -> {
JsInvokeProtos.RemoteJsRequest.Builder remoteRequest = JsInvokeProtos.RemoteJsRequest.newBuilder();
if (request.hasCompileRequest()) {
remoteRequest.setCompileRequest(request.getCompileRequest());
}
if (request.hasInvokeRequest()) {
remoteRequest.setInvokeRequest(request.getInvokeRequest());
}
if (request.hasReleaseRequest()) {
remoteRequest.setReleaseRequest(request.getReleaseRequest());
}
remoteRequest.setResponseTopic(responseTopic);
remoteRequest.setRequestIdMSB(requestId.getMostSignificantBits());
remoteRequest.setRequestIdLSB(requestId.getLeastSignificantBits());
return remoteRequest.build();
});
TBKafkaConsumerTemplate.TBKafkaConsumerTemplateBuilder<JsInvokeProtos.RemoteJsResponse> responseBuilder = TBKafkaConsumerTemplate.builder();
responseBuilder.settings(kafkaSettings);
responseBuilder.topic(responseTopicPrefix + "." + nodeIdProvider.getNodeId());
responseBuilder.clientId("js-" + nodeIdProvider.getNodeId());
responseBuilder.groupId("rule-engine-node-" + nodeIdProvider.getNodeId());
responseBuilder.autoCommit(true);
responseBuilder.autoCommitIntervalMs(autoCommitInterval);
responseBuilder.decoder(new RemoteJsResponseDecoder());
responseBuilder.requestIdExtractor((response) -> new UUID(response.getRequestIdMSB(), response.getRequestIdLSB()));
TbKafkaRequestTemplate.TbKafkaRequestTemplateBuilder
<JsInvokeProtos.RemoteJsRequest, JsInvokeProtos.RemoteJsResponse> builder = TbKafkaRequestTemplate.builder();
builder.requestTemplate(requestBuilder.build());
builder.responseTemplate(responseBuilder.build());
builder.maxPendingRequests(maxPendingRequests);
builder.maxRequestTimeout(maxRequestsTimeout);
builder.pollInterval(responsePollDuration);
kafkaTemplate = builder.build();
kafkaTemplate.init();
}
@PreDestroy
public void destroy() {
if (kafkaTemplate != null) {
kafkaTemplate.stop();
}
}
@Override
protected ListenableFuture<UUID> doEval(UUID scriptId, String functionName, String scriptBody) {
JsInvokeProtos.JsCompileRequest jsRequest = JsInvokeProtos.JsCompileRequest.newBuilder()
.setScriptIdMSB(scriptId.getMostSignificantBits())
.setScriptIdLSB(scriptId.getLeastSignificantBits())
.setFunctionName(functionName)
.setScriptBody(scriptBody).build();
JsInvokeProtos.RemoteJsRequest jsRequestWrapper = JsInvokeProtos.RemoteJsRequest.newBuilder()
.setCompileRequest(jsRequest)
.build();
log.trace("Post compile request for scriptId [{}]", scriptId);
ListenableFuture<JsInvokeProtos.RemoteJsResponse> future = kafkaTemplate.post(scriptId.toString(), jsRequestWrapper);
return Futures.transform(future, response -> {
JsInvokeProtos.JsCompileResponse compilationResult = response.getCompileResponse();
UUID compiledScriptId = new UUID(compilationResult.getScriptIdMSB(), compilationResult.getScriptIdLSB());
if (compilationResult.getSuccess()) {
scriptIdToNameMap.put(scriptId, functionName);
scriptIdToBodysMap.put(scriptId, scriptBody);
return compiledScriptId;
} else {
log.debug("[{}] Failed to compile script due to [{}]: {}", compiledScriptId, compilationResult.getErrorCode().name(), compilationResult.getErrorDetails());
throw new RuntimeException(compilationResult.getErrorDetails());
}
});
}
@Override
protected ListenableFuture<Object> doInvokeFunction(UUID scriptId, String functionName, Object[] args) {
String scriptBody = scriptIdToBodysMap.get(scriptId);
if (scriptBody == null) {
return Futures.immediateFailedFuture(new RuntimeException("No script body found for scriptId: [" + scriptId + "]!"));
}
JsInvokeProtos.JsInvokeRequest.Builder jsRequestBuilder = JsInvokeProtos.JsInvokeRequest.newBuilder()
.setScriptIdMSB(scriptId.getMostSignificantBits())
.setScriptIdLSB(scriptId.getLeastSignificantBits())
.setFunctionName(functionName)
.setTimeout((int) maxRequestsTimeout)
.setScriptBody(scriptIdToBodysMap.get(scriptId));
for (int i = 0; i < args.length; i++) {
jsRequestBuilder.addArgs(args[i].toString());
}
JsInvokeProtos.RemoteJsRequest jsRequestWrapper = JsInvokeProtos.RemoteJsRequest.newBuilder()
.setInvokeRequest(jsRequestBuilder.build())
.build();
ListenableFuture<JsInvokeProtos.RemoteJsResponse> future = kafkaTemplate.post(scriptId.toString(), jsRequestWrapper);
return Futures.transform(future, response -> {
JsInvokeProtos.JsInvokeResponse invokeResult = response.getInvokeResponse();
if (invokeResult.getSuccess()) {
return invokeResult.getResult();
} else {
log.debug("[{}] Failed to compile script due to [{}]: {}", scriptId, invokeResult.getErrorCode().name(), invokeResult.getErrorDetails());
throw new RuntimeException(invokeResult.getErrorDetails());
}
});
}
@Override
protected void doRelease(UUID scriptId, String functionName) throws Exception {
JsInvokeProtos.JsReleaseRequest jsRequest = JsInvokeProtos.JsReleaseRequest.newBuilder()
.setScriptIdMSB(scriptId.getMostSignificantBits())
.setScriptIdLSB(scriptId.getLeastSignificantBits())
.setFunctionName(functionName).build();
JsInvokeProtos.RemoteJsRequest jsRequestWrapper = JsInvokeProtos.RemoteJsRequest.newBuilder()
.setReleaseRequest(jsRequest)
.build();
ListenableFuture<JsInvokeProtos.RemoteJsResponse> future = kafkaTemplate.post(scriptId.toString(), jsRequestWrapper);
JsInvokeProtos.RemoteJsResponse response = future.get();
JsInvokeProtos.JsReleaseResponse compilationResult = response.getReleaseResponse();
UUID compiledScriptId = new UUID(compilationResult.getScriptIdMSB(), compilationResult.getScriptIdLSB());
if (compilationResult.getSuccess()) {
scriptIdToBodysMap.remove(scriptId);
} else {
log.debug("[{}] Failed to release script due", compiledScriptId);
}
}
}

21
common/message/src/main/java/org/thingsboard/server/common/msg/session/BasicAdaptorToSessionActorMsg.java → application/src/main/java/org/thingsboard/server/service/script/RemoteJsRequestEncoder.java

@ -13,20 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.msg.session;
package org.thingsboard.server.service.script;
public class BasicAdaptorToSessionActorMsg extends BasicSessionMsg implements AdaptorToSessionActorMsg {
private final FromDeviceMsg msg;
public BasicAdaptorToSessionActorMsg(SessionContext ctx, FromDeviceMsg msg) {
super(ctx);
this.msg = msg;
}
import org.thingsboard.server.gen.js.JsInvokeProtos;
import org.thingsboard.server.kafka.TbKafkaEncoder;
/**
* Created by ashvayka on 25.09.18.
*/
public class RemoteJsRequestEncoder implements TbKafkaEncoder<JsInvokeProtos.RemoteJsRequest> {
@Override
public FromDeviceMsg getMsg() {
return msg;
public byte[] encode(JsInvokeProtos.RemoteJsRequest value) {
return value.toByteArray();
}
}

32
application/src/main/java/org/thingsboard/server/service/script/RemoteJsResponseDecoder.java

@ -0,0 +1,32 @@
/**
* Copyright © 2016-2018 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.script;
import org.thingsboard.server.gen.js.JsInvokeProtos;
import org.thingsboard.server.kafka.TbKafkaDecoder;
import java.io.IOException;
/**
* Created by ashvayka on 25.09.18.
*/
public class RemoteJsResponseDecoder implements TbKafkaDecoder<JsInvokeProtos.RemoteJsResponse> {
@Override
public JsInvokeProtos.RemoteJsResponse decode(byte[] data) throws IOException {
return JsInvokeProtos.RemoteJsResponse.parseFrom(data);
}
}

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

@ -21,6 +21,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
@ -36,16 +37,22 @@ import java.util.concurrent.ExecutionException;
public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.ScriptEngine {
private static final ObjectMapper mapper = new ObjectMapper();
private final JsSandboxService sandboxService;
private final JsInvokeService sandboxService;
private final UUID scriptId;
private final EntityId entityId;
public RuleNodeJsScriptEngine(JsSandboxService sandboxService, String script, String... argNames) {
public RuleNodeJsScriptEngine(JsInvokeService sandboxService, EntityId entityId, String script, String... argNames) {
this.sandboxService = sandboxService;
this.entityId = entityId;
try {
this.scriptId = this.sandboxService.eval(JsScriptType.RULE_NODE_SCRIPT, script, argNames).get();
} catch (Exception e) {
throw new IllegalArgumentException("Can't compile script: " + e.getMessage(), e);
Throwable t = e;
if (e instanceof ExecutionException) {
t = e.getCause();
}
throw new IllegalArgumentException("Can't compile script: " + t.getMessage(), t);
}
}
@ -167,11 +174,13 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S
} catch (ExecutionException e) {
if (e.getCause() instanceof ScriptException) {
throw (ScriptException)e.getCause();
} else if (e.getCause() instanceof RuntimeException) {
throw new ScriptException(e.getCause().getMessage());
} else {
throw new ScriptException("Failed to execute js script: " + e.getMessage());
throw new ScriptException(e);
}
} catch (Exception e) {
throw new ScriptException("Failed to execute js script: " + e.getMessage());
throw new ScriptException(e);
}
}

2
application/src/main/java/org/thingsboard/server/service/script/RuleNodeScriptFactory.java

@ -28,7 +28,7 @@ public class RuleNodeScriptFactory {
" var metadata = JSON.parse(metadataStr); " +
" return JSON.stringify(%s(msg, metadata, msgType));" +
" function %s(%s, %s, %s) {";
private static final String JS_WRAPPER_SUFFIX = "}" +
private static final String JS_WRAPPER_SUFFIX = "\n}" +
"\n}";

31
application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java

@ -26,6 +26,7 @@ import org.springframework.stereotype.Component;
import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.exception.ThingsboardException;
@ -34,6 +35,7 @@ import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId;
@ -44,6 +46,7 @@ import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.user.UserService;
@ -66,6 +69,7 @@ public class AccessValidator {
public static final String CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION = "Customer user is not allowed to perform this operation!";
public static final String SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION = "System administrator is not allowed to perform this operation!";
public static final String DEVICE_WITH_REQUESTED_ID_NOT_FOUND = "Device with requested id wasn't found!";
public static final String ENTITY_VIEW_WITH_REQUESTED_ID_NOT_FOUND = "Entity-view with requested id wasn't found!";
@Autowired
protected TenantService tenantService;
@ -88,6 +92,9 @@ public class AccessValidator {
@Autowired
protected RuleChainService ruleChainService;
@Autowired
protected EntityViewService entityViewService;
private ExecutorService executor;
@PostConstruct
@ -158,6 +165,9 @@ public class AccessValidator {
case TENANT:
validateTenant(currentUser, entityId, callback);
return;
case ENTITY_VIEW:
validateEntityView(currentUser, entityId, callback);
return;
default:
//TODO: add support of other entities
throw new IllegalStateException("Not Implemented!");
@ -293,6 +303,27 @@ public class AccessValidator {
}
}
private void validateEntityView(final SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
if (currentUser.isSystemAdmin()) {
callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
} else {
ListenableFuture<EntityView> entityViewFuture = entityViewService.findEntityViewByIdAsync(new EntityViewId(entityId.getId()));
Futures.addCallback(entityViewFuture, getCallback(callback, entityView -> {
if (entityView == null) {
return ValidationResult.entityNotFound(ENTITY_VIEW_WITH_REQUESTED_ID_NOT_FOUND);
} else {
if (!entityView.getTenantId().equals(currentUser.getTenantId())) {
return ValidationResult.accessDenied("Entity-view doesn't belong to the current Tenant!");
} else if (currentUser.isCustomerUser() && !entityView.getCustomerId().equals(currentUser.getCustomerId())) {
return ValidationResult.accessDenied("Entity-view doesn't belong to the current Customer!");
} else {
return ValidationResult.ok(entityView);
}
}
}), executor);
}
}
private <T, V> FutureCallback<T> getCallback(FutureCallback<ValidationResult> callback, Function<T, ValidationResult<V>> transformer) {
return new FutureCallback<T>() {
@Override

47
application/src/main/java/org/thingsboard/server/service/security/ValidationCallback.java

@ -36,29 +36,10 @@ public class ValidationCallback<T> implements FutureCallback<ValidationResult> {
@Override
public void onSuccess(ValidationResult result) {
ValidationResultCode resultCode = result.getResultCode();
if (resultCode == ValidationResultCode.OK) {
if (result.getResultCode() == ValidationResultCode.OK) {
action.onSuccess(response);
} else {
Exception e;
switch (resultCode) {
case ENTITY_NOT_FOUND:
e = new EntityNotFoundException(result.getMessage());
break;
case UNAUTHORIZED:
e = new UnauthorizedException(result.getMessage());
break;
case ACCESS_DENIED:
e = new AccessDeniedException(result.getMessage());
break;
case INTERNAL_ERROR:
e = new InternalErrorException(result.getMessage());
break;
default:
e = new UnauthorizedException("Permission denied.");
break;
}
onFailure(e);
onFailure(getException(result));
}
}
@ -66,4 +47,28 @@ public class ValidationCallback<T> implements FutureCallback<ValidationResult> {
public void onFailure(Throwable e) {
action.onFailure(e);
}
public static Exception getException(ValidationResult result) {
ValidationResultCode resultCode = result.getResultCode();
Exception e;
switch (resultCode) {
case ENTITY_NOT_FOUND:
e = new EntityNotFoundException(result.getMessage());
break;
case UNAUTHORIZED:
e = new UnauthorizedException(result.getMessage());
break;
case ACCESS_DENIED:
e = new AccessDeniedException(result.getMessage());
break;
case INTERNAL_ERROR:
e = new InternalErrorException(result.getMessage());
break;
default:
e = new UnauthorizedException("Permission denied.");
break;
}
return e;
}
}

8
application/src/main/java/org/thingsboard/server/service/security/model/token/RawAccessJwtToken.java

@ -22,6 +22,7 @@ import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.UnsupportedJwtException;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.BadCredentialsException;
@ -29,12 +30,11 @@ import org.thingsboard.server.service.security.exception.JwtExpiredTokenExceptio
import java.io.Serializable;
@Slf4j
public class RawAccessJwtToken implements JwtToken, Serializable {
private static final long serialVersionUID = -797397445703066079L;
private static Logger logger = LoggerFactory.getLogger(RawAccessJwtToken.class);
private String token;
public RawAccessJwtToken(String token) {
@ -52,10 +52,10 @@ public class RawAccessJwtToken implements JwtToken, Serializable {
try {
return Jwts.parser().setSigningKey(signingKey).parseClaimsJws(this.token);
} catch (UnsupportedJwtException | MalformedJwtException | IllegalArgumentException | SignatureException ex) {
logger.error("Invalid JWT Token", ex);
log.error("Invalid JWT Token", ex);
throw new BadCredentialsException("Invalid JWT token: ", ex);
} catch (ExpiredJwtException expiredEx) {
logger.info("JWT Token is expired", expiredEx);
log.info("JWT Token is expired", expiredEx);
throw new JwtExpiredTokenException(this, "JWT Token expired", expiredEx);
}
}

50
application/src/main/java/org/thingsboard/server/service/session/DefaultDeviceSessionCacheService.java

@ -0,0 +1,50 @@
/**
* Copyright © 2016-2018 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.session;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceSessionsCacheEntry;
import java.util.ArrayList;
import java.util.Collections;
import static org.thingsboard.server.common.data.CacheConstants.SESSIONS_CACHE;
/**
* Created by ashvayka on 29.10.18.
*/
@Service
@Slf4j
public class DefaultDeviceSessionCacheService implements DeviceSessionCacheService {
@Override
@Cacheable(cacheNames = SESSIONS_CACHE, key = "#deviceId")
public DeviceSessionsCacheEntry get(DeviceId deviceId) {
log.debug("[{}] Fetching session data from cache", deviceId);
return DeviceSessionsCacheEntry.newBuilder().addAllSessions(Collections.emptyList()).build();
}
@Override
@CachePut(cacheNames = SESSIONS_CACHE, key = "#deviceId")
public DeviceSessionsCacheEntry put(DeviceId deviceId, DeviceSessionsCacheEntry sessions) {
log.debug("[{}] Pushing session data from cache: {}", deviceId, sessions);
return sessions;
}
}

20
common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicRequest.java → application/src/main/java/org/thingsboard/server/service/session/DeviceSessionCacheService.java

@ -13,24 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.msg.core;
package org.thingsboard.server.service.session;
import java.io.Serializable;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceSessionsCacheEntry;
/**
* @author Andrew Shvayka
* Created by ashvayka on 29.10.18.
*/
public class BasicRequest implements Serializable {
public interface DeviceSessionCacheService {
public static final Integer DEFAULT_REQUEST_ID = 0;
DeviceSessionsCacheEntry get(DeviceId deviceId);
private final Integer requestId;
DeviceSessionsCacheEntry put(DeviceId deviceId, DeviceSessionsCacheEntry sessions);
public BasicRequest(Integer requestId) {
this.requestId = requestId;
}
public Integer getRequestId() {
return requestId;
}
}

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

@ -43,6 +43,7 @@ import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.cluster.SendToClusterMsg;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
import org.thingsboard.server.dao.attributes.AttributesService;
@ -457,7 +458,7 @@ public class DefaultDeviceStateService implements DeviceStateService {
TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), msgType, stateData.getDeviceId(), stateData.getMetaData().copy(), TbMsgDataType.JSON
, json.writeValueAsString(state)
, null, null, 0L);
actorService.onMsg(new ServiceToRuleEngineMsg(stateData.getTenantId(), tbMsg));
actorService.onMsg(new SendToClusterMsg(stateData.getDeviceId(), new ServiceToRuleEngineMsg(stateData.getTenantId(), tbMsg)));
} catch (Exception e) {
log.warn("[{}] Failed to push inactivity alarm: {}", stateData.getDeviceId(), state, e);
}

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

@ -29,9 +29,11 @@ import org.thingsboard.rule.engine.api.util.DonAsynchron;
import org.thingsboard.server.actors.service.ActorService;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
@ -48,6 +50,8 @@ import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.msg.cluster.SendToClusterMsg;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
import org.thingsboard.server.service.cluster.routing.ClusterRoutingService;
@ -64,6 +68,7 @@ import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@ -101,6 +106,9 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
@Autowired
private ClusterRpcService rpcService;
@Autowired
private EntityViewService entityViewService;
@Autowired
@Lazy
private DeviceStateService stateService;
@ -133,20 +141,41 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
@Override
public void addLocalWsSubscription(String sessionId, EntityId entityId, SubscriptionState sub) {
long startTime = 0L;
long endTime = 0L;
if (entityId.getEntityType().equals(EntityType.ENTITY_VIEW) && TelemetryFeature.TIMESERIES.equals(sub.getType())) {
EntityView entityView = entityViewService.findEntityViewById(new EntityViewId(entityId.getId()));
entityId = entityView.getEntityId();
startTime = entityView.getStartTimeMs();
endTime = entityView.getEndTimeMs();
sub = getUpdatedSubscriptionState(entityId, sub, entityView);
}
Optional<ServerAddress> server = routingService.resolveById(entityId);
Subscription subscription;
if (server.isPresent()) {
ServerAddress address = server.get();
log.trace("[{}] Forwarding subscription [{}] for device [{}] to [{}]", sessionId, sub.getSubscriptionId(), entityId, address);
subscription = new Subscription(sub, true, address);
log.trace("[{}] Forwarding subscription [{}] for [{}] entity [{}] to [{}]", sessionId, sub.getSubscriptionId(), entityId.getEntityType().name(), entityId, address);
subscription = new Subscription(sub, true, address, startTime, endTime);
tellNewSubscription(address, sessionId, subscription);
} else {
log.trace("[{}] Registering local subscription [{}] for device [{}]", sessionId, sub.getSubscriptionId(), entityId);
subscription = new Subscription(sub, true);
log.trace("[{}] Registering local subscription [{}] for [{}] entity [{}]", sessionId, sub.getSubscriptionId(), entityId.getEntityType().name(), entityId);
subscription = new Subscription(sub, true, null, startTime, endTime);
}
registerSubscription(sessionId, entityId, subscription);
}
private SubscriptionState getUpdatedSubscriptionState(EntityId entityId, SubscriptionState sub, EntityView entityView) {
Map<String, Long> keyStates;
if (sub.isAllKeys()) {
keyStates = entityView.getKeys().getTimeseries().stream().collect(Collectors.toMap(k -> k, k -> 0L));
} else {
keyStates = sub.getKeyStates().entrySet()
.stream().filter(entry -> entityView.getKeys().getTimeseries().contains(entry.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
return new SubscriptionState(sub.getWsSessionId(), sub.getSubscriptionId(), entityId, sub.getType(), false, keyStates, sub.getScope());
}
@Override
public void cleanupLocalWsSessionSubscriptions(TelemetryWebSocketSessionRef sessionRef, String sessionId) {
cleanupLocalWsSessionSubscriptions(sessionId);
@ -232,7 +261,7 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
new SubscriptionState(proto.getSessionId(), proto.getSubscriptionId(),
EntityIdFactory.getByTypeAndId(proto.getEntityType(), proto.getEntityId()),
TelemetryFeature.valueOf(proto.getType()), proto.getAllKeys(), statesMap, proto.getScope()),
false, new ServerAddress(serverAddress.getHost(), serverAddress.getPort()));
false, new ServerAddress(serverAddress.getHost(), serverAddress.getPort(), serverAddress.getServerType()));
addRemoteWsSubscription(serverAddress, proto.getSessionId(), subscription);
}
@ -430,7 +459,7 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
onLocalSubUpdate(entityId, s -> TelemetryFeature.TIMESERIES == s.getType(), s -> {
List<TsKvEntry> subscriptionUpdate = null;
for (TsKvEntry kv : ts) {
if (s.isAllKeys() || s.getKeyStates().containsKey((kv.getKey()))) {
if (isInTimeRange(s, kv.getTs()) && (s.isAllKeys() || s.getKeyStates().containsKey((kv.getKey())))) {
if (subscriptionUpdate == null) {
subscriptionUpdate = new ArrayList<>();
}
@ -441,6 +470,11 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
});
}
private boolean isInTimeRange(Subscription subscription, long kvTime) {
return (subscription.getStartTime() == 0 || subscription.getStartTime() <= kvTime)
&& (subscription.getEndTime() == 0 || subscription.getEndTime() >= kvTime);
}
private void onLocalSubUpdate(EntityId entityId, Predicate<Subscription> filter, Function<Subscription, List<TsKvEntry>> f) {
Set<Subscription> deviceSubscriptions = subscriptionsByEntityId.get(entityId);
if (deviceSubscriptions != null) {
@ -584,7 +618,9 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
builder.setEntityId(sub.getEntityId().getId().toString());
builder.setType(sub.getType().name());
builder.setAllKeys(sub.isAllKeys());
builder.setScope(sub.getScope());
if (sub.getScope() != null) {
builder.setScope(sub.getScope());
}
sub.getKeyStates().entrySet().forEach(e -> builder.addKeyStates(
ClusterAPIProtos.SubscriptionKetStateProto.newBuilder().setKey(e.getKey()).setTs(e.getValue()).build()));
rpcService.tell(address, ClusterAPIProtos.MessageType.CLUSTER_TELEMETRY_SUBSCRIPTION_CREATE_MESSAGE, builder.build().toByteArray());

14
application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java

@ -37,13 +37,18 @@ import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.service.security.AccessValidator;
import org.thingsboard.server.service.security.ValidationCallback;
import org.thingsboard.server.service.security.ValidationResult;
import org.thingsboard.server.service.security.ValidationResultCode;
import org.thingsboard.server.service.telemetry.cmd.AttributesSubscriptionCmd;
import org.thingsboard.server.service.telemetry.cmd.GetHistoryCmd;
import org.thingsboard.server.service.telemetry.cmd.SubscriptionCmd;
import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmd;
import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper;
import org.thingsboard.server.service.telemetry.cmd.TimeseriesSubscriptionCmd;
import org.thingsboard.server.service.telemetry.exception.AccessDeniedException;
import org.thingsboard.server.service.telemetry.exception.EntityNotFoundException;
import org.thingsboard.server.service.telemetry.exception.InternalErrorException;
import org.thingsboard.server.service.telemetry.exception.UnauthorizedException;
import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode;
import org.thingsboard.server.service.telemetry.sub.SubscriptionState;
@ -535,11 +540,16 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
};
}
private FutureCallback<ValidationResult> on(Consumer<ValidationResult> success, Consumer<Throwable> failure) {
private FutureCallback<ValidationResult> on(Consumer<Void> success, Consumer<Throwable> failure) {
return new FutureCallback<ValidationResult>() {
@Override
public void onSuccess(@Nullable ValidationResult result) {
success.accept(result);
ValidationResultCode resultCode = result.getResultCode();
if (resultCode == ValidationResultCode.OK) {
success.accept(null);
} else {
onFailure(ValidationCallback.getException(result));
}
}
@Override

6
application/src/main/java/org/thingsboard/server/service/telemetry/sub/Subscription.java

@ -30,9 +30,11 @@ public class Subscription {
private final SubscriptionState sub;
private final boolean local;
private ServerAddress server;
private long startTime;
private long endTime;
public Subscription(SubscriptionState sub, boolean local) {
this(sub, local, null);
public Subscription(SubscriptionState sub, boolean local, ServerAddress server) {
this(sub, local, server, 0L, 0L);
}
public String getWsSessionId() {

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

Loading…
Cancel
Save