Browse Source

Merge pull request #794 from thingsboard/develop/2.0

TB 2.0 merged to master
pull/917/head
Andrew Shvayka 8 years ago
committed by GitHub
parent
commit
9fce3e068e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 57
      application/pom.xml
  2. 2
      application/src/main/conf/thingsboard.conf
  3. 13
      application/src/main/data/json/demo/plugins/demo_device_messaging_rpc_plugin.json
  4. 28
      application/src/main/data/json/demo/plugins/demo_email_plugin.json
  5. 11
      application/src/main/data/json/demo/plugins/demo_time_rpc_plugin.json
  6. 46
      application/src/main/data/json/demo/rules/demo_alarm_rule.json
  7. 35
      application/src/main/data/json/demo/rules/demo_gettime_rpc_rule.json
  8. 38
      application/src/main/data/json/demo/rules/demo_messaging_rpc_rule.json
  9. 11
      application/src/main/data/json/system/plugins/system_rpc_plugin.json
  10. 9
      application/src/main/data/json/system/plugins/system_telemetry_plugin.json
  11. 29
      application/src/main/data/json/system/rules/system_telemetry_rule.json
  12. 98
      application/src/main/data/json/tenant/rule_chains/root_rule_chain.json
  13. 103
      application/src/main/data/upgrade/2.0.0/schema_update.cql
  14. 44
      application/src/main/data/upgrade/2.0.0/schema_update.sql
  15. 263
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  16. 153
      application/src/main/java/org/thingsboard/server/actors/app/AppActor.java
  17. 78
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java
  18. 449
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
  19. 37
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActorToRuleEngineMsg.java
  20. 40
      application/src/main/java/org/thingsboard/server/actors/device/PendingSessionMsgData.java
  21. 36
      application/src/main/java/org/thingsboard/server/actors/device/RuleEngineQueuePutAckMsg.java
  22. 1
      application/src/main/java/org/thingsboard/server/actors/device/SessionInfo.java
  23. 4
      application/src/main/java/org/thingsboard/server/actors/device/ToDeviceRpcRequestMetadata.java
  24. 24
      application/src/main/java/org/thingsboard/server/actors/device/ToServerRpcRequestMetadata.java
  25. 151
      application/src/main/java/org/thingsboard/server/actors/plugin/PluginActor.java
  26. 251
      application/src/main/java/org/thingsboard/server/actors/plugin/PluginActorMessageProcessor.java
  27. 53
      application/src/main/java/org/thingsboard/server/actors/plugin/PluginCallbackMessage.java
  28. 546
      application/src/main/java/org/thingsboard/server/actors/plugin/PluginProcessingContext.java
  29. 30
      application/src/main/java/org/thingsboard/server/actors/plugin/PluginTerminationMsg.java
  30. 66
      application/src/main/java/org/thingsboard/server/actors/plugin/RuleToPluginMsgWrapper.java
  31. 141
      application/src/main/java/org/thingsboard/server/actors/plugin/SharedPluginProcessingContext.java
  32. 48
      application/src/main/java/org/thingsboard/server/actors/plugin/ValidationResult.java
  33. 110
      application/src/main/java/org/thingsboard/server/actors/rpc/BasicRpcSessionListener.java
  34. 2
      application/src/main/java/org/thingsboard/server/actors/rpc/RpcBroadcastMsg.java
  35. 59
      application/src/main/java/org/thingsboard/server/actors/rpc/RpcManagerActor.java
  36. 30
      application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionActor.java
  37. 2
      application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionCreateRequestMsg.java
  38. 4
      application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionTellMsg.java
  39. 117
      application/src/main/java/org/thingsboard/server/actors/rule/ChainProcessingContext.java
  40. 41
      application/src/main/java/org/thingsboard/server/actors/rule/ChainProcessingMetaData.java
  41. 43
      application/src/main/java/org/thingsboard/server/actors/rule/ComplexRuleActorChain.java
  42. 90
      application/src/main/java/org/thingsboard/server/actors/rule/RuleActor.java
  43. 354
      application/src/main/java/org/thingsboard/server/actors/rule/RuleActorMessageProcessor.java
  44. 107
      application/src/main/java/org/thingsboard/server/actors/rule/RuleActorMetaData.java
  45. 115
      application/src/main/java/org/thingsboard/server/actors/rule/RuleProcessingContext.java
  46. 241
      application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
  47. 97
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActor.java
  48. 297
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java
  49. 57
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainManagerActor.java
  50. 40
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainToRuleChainMsg.java
  51. 38
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainToRuleNodeMsg.java
  52. 109
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActor.java
  53. 121
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java
  54. 19
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeCtx.java
  55. 17
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeRelation.java
  56. 34
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeToRuleChainTellNextMsg.java
  57. 37
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeToSelfErrorMsg.java
  58. 36
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeToSelfMsg.java
  59. 13
      application/src/main/java/org/thingsboard/server/actors/service/ActorService.java
  60. 5
      application/src/main/java/org/thingsboard/server/actors/service/ComponentActor.java
  61. 24
      application/src/main/java/org/thingsboard/server/actors/service/ContextAwareActor.java
  62. 3
      application/src/main/java/org/thingsboard/server/actors/service/ContextBasedCreator.java
  63. 184
      application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java
  64. 24
      application/src/main/java/org/thingsboard/server/actors/service/WebSocketMsgProcessor.java
  65. 37
      application/src/main/java/org/thingsboard/server/actors/session/ASyncMsgProcessor.java
  66. 59
      application/src/main/java/org/thingsboard/server/actors/session/AbstractSessionActorMsgProcessor.java
  67. 52
      application/src/main/java/org/thingsboard/server/actors/session/SessionActor.java
  68. 29
      application/src/main/java/org/thingsboard/server/actors/session/SessionManagerActor.java
  69. 20
      application/src/main/java/org/thingsboard/server/actors/session/SyncMsgProcessor.java
  70. 52
      application/src/main/java/org/thingsboard/server/actors/shared/AbstractContextAwareMsgProcessor.java
  71. 66
      application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java
  72. 84
      application/src/main/java/org/thingsboard/server/actors/shared/EntityActorsManager.java
  73. 9
      application/src/main/java/org/thingsboard/server/actors/shared/SessionTimeoutMsg.java
  74. 77
      application/src/main/java/org/thingsboard/server/actors/shared/plugin/PluginManager.java
  75. 45
      application/src/main/java/org/thingsboard/server/actors/shared/plugin/SystemPluginManager.java
  76. 135
      application/src/main/java/org/thingsboard/server/actors/shared/rule/RuleManager.java
  77. 48
      application/src/main/java/org/thingsboard/server/actors/shared/rule/TenantRuleManager.java
  78. 59
      application/src/main/java/org/thingsboard/server/actors/shared/rulechain/RuleChainManager.java
  79. 24
      application/src/main/java/org/thingsboard/server/actors/shared/rulechain/SystemRuleChainManager.java
  80. 21
      application/src/main/java/org/thingsboard/server/actors/shared/rulechain/TenantRuleChainManager.java
  81. 7
      application/src/main/java/org/thingsboard/server/actors/stats/StatsActor.java
  82. 40
      application/src/main/java/org/thingsboard/server/actors/tenant/RuleChainDeviceMsg.java
  83. 177
      application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
  84. 2
      application/src/main/java/org/thingsboard/server/config/AuditLogLevelProperties.java
  85. 6
      application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java
  86. 10
      application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java
  87. 18
      application/src/main/java/org/thingsboard/server/config/WebSocketConfiguration.java
  88. 11
      application/src/main/java/org/thingsboard/server/controller/AdminController.java
  89. 26
      application/src/main/java/org/thingsboard/server/controller/AlarmController.java
  90. 15
      application/src/main/java/org/thingsboard/server/controller/AssetController.java
  91. 9
      application/src/main/java/org/thingsboard/server/controller/AuditLogController.java
  92. 14
      application/src/main/java/org/thingsboard/server/controller/AuthController.java
  93. 218
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  94. 23
      application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java
  95. 11
      application/src/main/java/org/thingsboard/server/controller/CustomerController.java
  96. 19
      application/src/main/java/org/thingsboard/server/controller/DashboardController.java
  97. 20
      application/src/main/java/org/thingsboard/server/controller/DeviceController.java
  98. 14
      application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java
  99. 14
      application/src/main/java/org/thingsboard/server/controller/EventController.java
  100. 32
      application/src/main/java/org/thingsboard/server/controller/HttpValidationCallback.java

57
application/pom.xml

@ -20,10 +20,9 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>1.4.1-SNAPSHOT</version>
<version>2.0.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<groupId>org.thingsboard</groupId>
<artifactId>application</artifactId>
<packaging>jar</packaging>
@ -50,12 +49,12 @@
<classifier>linux-x86_64</classifier>
</dependency>
<dependency>
<groupId>org.thingsboard</groupId>
<artifactId>extensions-api</artifactId>
<groupId>org.thingsboard.rule-engine</groupId>
<artifactId>rule-engine-api</artifactId>
</dependency>
<dependency>
<groupId>org.thingsboard</groupId>
<artifactId>extensions-core</artifactId>
<groupId>org.thingsboard.rule-engine</groupId>
<artifactId>rule-engine-components</artifactId>
</dependency>
<dependency>
<groupId>org.thingsboard.common</groupId>
@ -257,6 +256,10 @@
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
</dependency>
<dependency>
<groupId>org.javadelight</groupId>
<artifactId>delight-nashorn-sandbox</artifactId>
</dependency>
</dependencies>
<build>
@ -463,48 +466,6 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-extensions</id>
<phase>package</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/extensions</outputDirectory>
<artifactItems>
<artifactItem>
<groupId>org.thingsboard.extensions</groupId>
<artifactId>extension-rabbitmq</artifactId>
<classifier>extension</classifier>
</artifactItem>
<artifactItem>
<groupId>org.thingsboard.extensions</groupId>
<artifactId>extension-rest-api-call</artifactId>
<classifier>extension</classifier>
</artifactItem>
<artifactItem>
<groupId>org.thingsboard.extensions</groupId>
<artifactId>extension-kafka</artifactId>
<classifier>extension</classifier>
</artifactItem>
<artifactItem>
<groupId>org.thingsboard.extensions</groupId>
<artifactId>extension-mqtt</artifactId>
<classifier>extension</classifier>
</artifactItem>
<artifactItem>
<groupId>org.thingsboard.extensions</groupId>
<artifactId>extension-sqs</artifactId>
<classifier>extension</classifier>
</artifactItem>
<artifactItem>
<groupId>org.thingsboard.extensions</groupId>
<artifactId>extension-sns</artifactId>
<classifier>extension</classifier>
</artifactItem>
</artifactItems>
</configuration>
</execution>
<execution>
<id>copy-winsw-service</id>
<phase>package</phase>

2
application/src/main/conf/thingsboard.conf

@ -14,7 +14,7 @@
# limitations under the License.
#
export JAVA_OPTS="$JAVA_OPTS -Dplatform=@pkg.platform@"
export JAVA_OPTS="$JAVA_OPTS -Dplatform=@pkg.platform@ -Dinstall.data_dir=@pkg.installFolder@"
export LOG_FILENAME=${pkg.name}.out
export LOADER_PATH=${pkg.installFolder}/conf,${pkg.installFolder}/extensions
export SQL_DATA_FOLDER=${pkg.installFolder}/data/sql

13
application/src/main/data/json/demo/plugins/demo_device_messaging_rpc_plugin.json

@ -1,13 +0,0 @@
{
"apiToken": "messaging",
"name": "Demo Device Messaging RPC Plugin",
"clazz": "org.thingsboard.server.extensions.core.plugin.messaging.DeviceMessagingPlugin",
"publicAccess": false,
"state": "ACTIVE",
"configuration": {
"maxDeviceCountPerCustomer": 1024,
"defaultTimeout": 20000,
"maxTimeout": 60000
},
"additionalInfo": null
}

28
application/src/main/data/json/demo/plugins/demo_email_plugin.json

@ -1,28 +0,0 @@
{
"apiToken": "mail",
"name": "Demo Email Plugin",
"clazz": "org.thingsboard.server.extensions.core.plugin.mail.MailPlugin",
"publicAccess": true,
"state": "ACTIVE",
"configuration": {
"host": "smtp.sendgrid.net",
"port": 2525,
"username": "apikey",
"password": "your_api_key",
"otherProperties": [
{
"key": "mail.smtp.auth",
"value": "true"
},
{
"key": "mail.smtp.timeout",
"value": "10000"
},
{
"key": "mail.smtp.starttls.enable",
"value": "true"
}
]
},
"additionalInfo": null
}

11
application/src/main/data/json/demo/plugins/demo_time_rpc_plugin.json

@ -1,11 +0,0 @@
{
"apiToken": "time",
"name": "Demo Time RPC Plugin",
"clazz": "org.thingsboard.server.extensions.core.plugin.time.TimePlugin",
"publicAccess": false,
"state": "ACTIVE",
"configuration": {
"timeFormat": "yyyy MM dd HH:mm:ss.SSS"
},
"additionalInfo": null
}

46
application/src/main/data/json/demo/rules/demo_alarm_rule.json

@ -1,46 +0,0 @@
{
"name": "Demo Alarm Rule",
"state": "ACTIVE",
"weight": 0,
"pluginToken": "mail",
"filters": [
{
"clazz": "org.thingsboard.server.extensions.core.filter.MsgTypeFilter",
"name": "MsgTypeFilter",
"configuration": {
"messageTypes": [
"POST_TELEMETRY",
"POST_ATTRIBUTES",
"GET_ATTRIBUTES"
]
}
},
{
"clazz": "org.thingsboard.server.extensions.core.filter.DeviceTelemetryFilter",
"name": "Temperature filter",
"configuration": {
"filter": "typeof temperature !== 'undefined' && temperature >= 100"
}
}
],
"processor": {
"clazz": "org.thingsboard.server.extensions.core.processor.AlarmDeduplicationProcessor",
"name": "AlarmDeduplicationProcessor",
"configuration": {
"alarmIdTemplate": "[$date.get('yyyy-MM-dd HH:mm')] Device $cs.get('serialNumber')($cs.get('model')) temperature is high!",
"alarmBodyTemplate": "[$date.get('yyyy-MM-dd HH:mm:ss')] Device $cs.get('serialNumber')($cs.get('model')) temperature is $temp.valueAsString!"
}
},
"action": {
"clazz": "org.thingsboard.server.extensions.core.action.mail.SendMailAction",
"name": "Send Mail Action",
"configuration": {
"sendFlag": "isNewAlarm",
"fromTemplate": "thingsboard@gmail.com",
"toTemplate": "thingsboard@gmail.com",
"subjectTemplate": "$alarmId",
"bodyTemplate": "$alarmBody"
}
},
"additionalInfo": null
}

35
application/src/main/data/json/demo/rules/demo_gettime_rpc_rule.json

@ -1,35 +0,0 @@
{
"name": "Demo getTime RPC Rule",
"state": "ACTIVE",
"weight": 0,
"pluginToken": "time",
"filters": [
{
"configuration": {
"messageTypes": [
"RPC_REQUEST"
]
},
"name": "RPC Request Filter",
"clazz": "org.thingsboard.server.extensions.core.filter.MsgTypeFilter"
},
{
"configuration": {
"methodNames": [
{
"name": "getTime"
}
]
},
"name": "getTime method filter",
"clazz": "org.thingsboard.server.extensions.core.filter.MethodNameFilter"
}
],
"processor": null,
"action": {
"configuration": {},
"clazz": "org.thingsboard.server.extensions.core.action.rpc.RpcPluginAction",
"name": "getTimeAction"
},
"additionalInfo": null
}

38
application/src/main/data/json/demo/rules/demo_messaging_rpc_rule.json

@ -1,38 +0,0 @@
{
"name": "Demo Messaging RPC Rule",
"state": "ACTIVE",
"weight": 0,
"pluginToken": "messaging",
"filters": [
{
"configuration": {
"messageTypes": [
"RPC_REQUEST"
]
},
"name": "RPC Request Filter",
"clazz": "org.thingsboard.server.extensions.core.filter.MsgTypeFilter"
},
{
"configuration": {
"methodNames": [
{
"name": "getDevices"
},
{
"name": "sendMsg"
}
]
},
"name": "Messaging methods filter",
"clazz": "org.thingsboard.server.extensions.core.filter.MethodNameFilter"
}
],
"processor": null,
"action": {
"configuration": {},
"clazz": "org.thingsboard.server.extensions.core.action.rpc.RpcPluginAction",
"name": "Messaging RPC Action"
},
"additionalInfo": null
}

11
application/src/main/data/json/system/plugins/system_rpc_plugin.json

@ -1,11 +0,0 @@
{
"apiToken": "rpc",
"name": "System RPC Plugin",
"clazz": "org.thingsboard.server.extensions.core.plugin.rpc.RpcPlugin",
"publicAccess": true,
"state": "ACTIVE",
"configuration": {
"defaultTimeout": 20000
},
"additionalInfo": null
}

9
application/src/main/data/json/system/plugins/system_telemetry_plugin.json

@ -1,9 +0,0 @@
{
"apiToken": "telemetry",
"name": "System Telemetry Plugin",
"clazz": "org.thingsboard.server.extensions.core.plugin.telemetry.TelemetryStoragePlugin",
"publicAccess": true,
"state": "ACTIVE",
"configuration": {},
"additionalInfo": null
}

29
application/src/main/data/json/system/rules/system_telemetry_rule.json

@ -1,29 +0,0 @@
{
"name": "System Telemetry Rule",
"state": "ACTIVE",
"weight": 0,
"pluginToken": "telemetry",
"filters": [
{
"clazz": "org.thingsboard.server.extensions.core.filter.MsgTypeFilter",
"name": "TelemetryFilter",
"configuration": {
"messageTypes": [
"POST_TELEMETRY",
"POST_ATTRIBUTES",
"GET_ATTRIBUTES"
]
}
}
],
"processor": null,
"action": {
"clazz": "org.thingsboard.server.extensions.core.action.telemetry.TelemetryPluginAction",
"name": "TelemetryMsgConverterAction",
"configuration": {
"timeUnit": "DAYS",
"ttlValue": 365
}
},
"additionalInfo": null
}

98
application/src/main/data/json/tenant/rule_chains/root_rule_chain.json

@ -0,0 +1,98 @@
{
"ruleChain": {
"additionalInfo": null,
"name": "Root Rule Chain",
"firstRuleNodeId": null,
"root": true,
"debugMode": false,
"configuration": null
},
"metadata": {
"firstNodeIndex": 2,
"nodes": [
{
"additionalInfo": {
"layoutX": 824,
"layoutY": 156
},
"type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode",
"name": "SaveTS",
"debugMode": false,
"configuration": {
"defaultTTL": 0
}
},
{
"additionalInfo": {
"layoutX": 825,
"layoutY": 52
},
"type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode",
"name": "save client attributes",
"debugMode": false,
"configuration": {
"scope": "CLIENT_SCOPE"
}
},
{
"additionalInfo": {
"layoutX": 347,
"layoutY": 149
},
"type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode",
"name": "Message Type Switch",
"debugMode": false,
"configuration": {
"version": 0
}
},
{
"additionalInfo": {
"layoutX": 825,
"layoutY": 266
},
"type": "org.thingsboard.rule.engine.action.TbLogNode",
"name": "Log RPC",
"debugMode": false,
"configuration": {
"jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
}
},
{
"additionalInfo": {
"layoutX": 825,
"layoutY": 379
},
"type": "org.thingsboard.rule.engine.action.TbLogNode",
"name": "Log Other",
"debugMode": false,
"configuration": {
"jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
}
}
],
"connections": [
{
"fromIndex": 2,
"toIndex": 4,
"type": "Other"
},
{
"fromIndex": 2,
"toIndex": 1,
"type": "Post attributes"
},
{
"fromIndex": 2,
"toIndex": 0,
"type": "Post telemetry"
},
{
"fromIndex": 2,
"toIndex": 3,
"type": "RPC Request"
}
],
"ruleChainConnections": null
}
}

103
application/src/main/data/upgrade/2.0.0/schema_update.cql

@ -0,0 +1,103 @@
--
-- 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.
--
CREATE TABLE IF NOT EXISTS thingsboard.msg_queue (
node_id timeuuid,
cluster_partition bigint,
ts_partition bigint,
ts bigint,
msg blob,
PRIMARY KEY ((node_id, cluster_partition, ts_partition), ts))
WITH CLUSTERING ORDER BY (ts DESC)
AND compaction = {
'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy',
'min_threshold': '5',
'base_time_seconds': '43200',
'max_window_size_seconds': '43200',
'tombstone_threshold': '0.9',
'unchecked_tombstone_compaction': 'true'
};
CREATE TABLE IF NOT EXISTS thingsboard.msg_ack_queue (
node_id timeuuid,
cluster_partition bigint,
ts_partition bigint,
msg_id timeuuid,
PRIMARY KEY ((node_id, cluster_partition, ts_partition), msg_id))
WITH CLUSTERING ORDER BY (msg_id DESC)
AND compaction = {
'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy',
'min_threshold': '5',
'base_time_seconds': '43200',
'max_window_size_seconds': '43200',
'tombstone_threshold': '0.9',
'unchecked_tombstone_compaction': 'true'
};
CREATE TABLE IF NOT EXISTS thingsboard.processed_msg_partitions (
node_id timeuuid,
cluster_partition bigint,
ts_partition bigint,
PRIMARY KEY ((node_id, cluster_partition), ts_partition))
WITH CLUSTERING ORDER BY (ts_partition DESC)
AND compaction = {
'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy',
'min_threshold': '5',
'base_time_seconds': '43200',
'max_window_size_seconds': '43200',
'tombstone_threshold': '0.9',
'unchecked_tombstone_compaction': 'true'
};
CREATE TABLE IF NOT EXISTS thingsboard.rule_chain (
id uuid,
tenant_id uuid,
name text,
search_text text,
first_rule_node_id uuid,
root boolean,
debug_mode boolean,
configuration text,
additional_info text,
PRIMARY KEY (id, tenant_id)
);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.rule_chain_by_tenant_and_search_text AS
SELECT *
from thingsboard.rule_chain
WHERE tenant_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
PRIMARY KEY ( tenant_id, search_text, id )
WITH CLUSTERING ORDER BY ( search_text ASC, id DESC );
CREATE TABLE IF NOT EXISTS thingsboard.rule_node (
id uuid,
rule_chain_id uuid,
type text,
name text,
debug_mode boolean,
search_text text,
configuration text,
additional_info text,
PRIMARY KEY (id)
);
DROP MATERIALIZED VIEW IF EXISTS thingsboard.rule_by_plugin_token;
DROP MATERIALIZED VIEW IF EXISTS thingsboard.rule_by_tenant_and_search_text;
DROP MATERIALIZED VIEW IF EXISTS thingsboard.plugin_by_api_token;
DROP MATERIALIZED VIEW IF EXISTS thingsboard.plugin_by_tenant_and_search_text;
DROP TABLE IF EXISTS thingsboard.rule;
DROP TABLE IF EXISTS thingsboard.plugin;

44
application/src/main/data/upgrade/2.0.0/schema_update.sql

@ -0,0 +1,44 @@
--
-- 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.
--
CREATE TABLE IF NOT EXISTS rule_chain (
id varchar(31) NOT NULL CONSTRAINT rule_chain_pkey PRIMARY KEY,
additional_info varchar,
configuration varchar(10000000),
name varchar(255),
first_rule_node_id varchar(31),
root boolean,
debug_mode boolean,
search_text varchar(255),
tenant_id varchar(31)
);
CREATE TABLE IF NOT EXISTS rule_node (
id varchar(31) NOT NULL CONSTRAINT rule_node_pkey PRIMARY KEY,
rule_chain_id varchar(31),
additional_info varchar,
configuration varchar(10000000),
type varchar(255),
name varchar(255),
debug_mode boolean,
search_text varchar(255)
);
DROP TABLE rule;
DROP TABLE plugin;
DELETE FROM alarm WHERE originator_type = 3 OR originator_type = 4;
UPDATE alarm SET originator_type = (originator_type - 2) where originator_type > 2;

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

@ -21,22 +21,27 @@ import akka.actor.Scheduler;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import lombok.Getter;
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.stereotype.Component;
import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.server.actors.service.ActorService;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Event;
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.TbMsg;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.transport.auth.DeviceAuthService;
import org.thingsboard.server.controller.plugin.PluginWebSocketMsgEndpoint;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService;
@ -44,118 +49,222 @@ 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.event.EventService;
import org.thingsboard.server.dao.plugin.PluginService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.service.cluster.discovery.DiscoveryService;
import org.thingsboard.server.service.cluster.routing.ClusterRoutingService;
import org.thingsboard.server.service.cluster.rpc.ClusterRpcService;
import org.thingsboard.server.service.component.ComponentDiscoveryService;
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.state.DeviceStateService;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Optional;
@Slf4j
@Component
public class ActorSystemContext {
private static final String AKKA_CONF_FILE_NAME = "actor-system.conf";
protected final ObjectMapper mapper = new ObjectMapper();
@Getter @Setter private ActorService actorService;
@Getter
@Setter
private ActorService actorService;
@Autowired
@Getter
private DiscoveryService discoveryService;
@Autowired
@Getter
@Setter
private ComponentDiscoveryService componentService;
@Autowired
@Getter
private ClusterRoutingService routingService;
@Autowired
@Getter
private ClusterRpcService rpcService;
@Autowired
@Getter
private DataDecodingEncodingService encodingService;
@Autowired
@Getter
private DeviceAuthService deviceAuthService;
@Autowired
@Getter
private DeviceService deviceService;
@Autowired
@Getter
private AssetService assetService;
@Autowired
@Getter
private TenantService tenantService;
@Autowired
@Getter
private CustomerService customerService;
@Autowired
@Getter private DiscoveryService discoveryService;
@Getter
private UserService userService;
@Autowired
@Getter @Setter private ComponentDiscoveryService componentService;
@Getter
private RuleChainService ruleChainService;
@Autowired
@Getter private ClusterRoutingService routingService;
@Getter
private TimeseriesService tsService;
@Autowired
@Getter private ClusterRpcService rpcService;
@Getter
private AttributesService attributesService;
@Autowired
@Getter private DeviceAuthService deviceAuthService;
@Getter
private EventService eventService;
@Autowired
@Getter private DeviceService deviceService;
@Getter
private AlarmService alarmService;
@Autowired
@Getter private AssetService assetService;
@Getter
private RelationService relationService;
@Autowired
@Getter private TenantService tenantService;
@Getter
private AuditLogService auditLogService;
@Autowired
@Getter private CustomerService customerService;
@Getter
private TelemetrySubscriptionService tsSubService;
@Autowired
@Getter private RuleService ruleService;
@Getter
private DeviceRpcService deviceRpcService;
@Autowired
@Getter private PluginService pluginService;
@Getter
private JsSandboxService jsSandbox;
@Autowired
@Getter private TimeseriesService tsService;
@Getter
private JsExecutorService jsExecutor;
@Autowired
@Getter private AttributesService attributesService;
@Getter
private MailExecutorService mailExecutor;
@Autowired
@Getter private EventService eventService;
@Getter
private DbCallbackExecutorService dbCallbackExecutor;
@Autowired
@Getter private AlarmService alarmService;
@Getter
private ExternalCallExecutorService externalCallExecutorService;
@Autowired
@Getter private RelationService relationService;
@Getter
private MailService mailService;
@Autowired
@Getter private AuditLogService auditLogService;
@Getter
private MsgQueueService msgQueueService;
@Autowired
@Getter @Setter private PluginWebSocketMsgEndpoint wsMsgEndpoint;
@Getter
private DeviceStateService deviceStateService;
@Value("${cluster.partition_id}")
@Getter
private long queuePartitionId;
@Value("${actors.session.max_concurrent_sessions_per_device:1}")
@Getter
private long maxConcurrentSessionsPerDevice;
@Value("${actors.session.sync.timeout}")
@Getter private long syncSessionTimeout;
@Getter
private long syncSessionTimeout;
@Value("${actors.plugin.termination.delay}")
@Getter private long pluginActorTerminationDelay;
@Value("${actors.queue.enabled}")
@Getter
private boolean queuePersistenceEnabled;
@Value("${actors.plugin.processing.timeout}")
@Getter private long pluginProcessingTimeout;
@Value("${actors.queue.timeout}")
@Getter
private long queuePersistenceTimeout;
@Value("${actors.plugin.error_persist_frequency}")
@Getter private long pluginErrorPersistFrequency;
@Value("${actors.client_side_rpc.timeout}")
@Getter
private long clientSideRpcTimeout;
@Value("${actors.rule.termination.delay}")
@Getter private long ruleActorTerminationDelay;
@Value("${actors.rule.chain.error_persist_frequency}")
@Getter
private long ruleChainErrorPersistFrequency;
@Value("${actors.rule.error_persist_frequency}")
@Getter private long ruleErrorPersistFrequency;
@Value("${actors.rule.node.error_persist_frequency}")
@Getter
private long ruleNodeErrorPersistFrequency;
@Value("${actors.statistics.enabled}")
@Getter private boolean statisticsEnabled;
@Getter
private boolean statisticsEnabled;
@Value("${actors.statistics.persist_frequency}")
@Getter private long statisticsPersistFrequency;
@Getter
private long statisticsPersistFrequency;
@Value("${actors.tenant.create_components_on_init}")
@Getter private boolean tenantComponentsInitEnabled;
@Getter
private boolean tenantComponentsInitEnabled;
@Getter @Setter private ActorSystem actorSystem;
@Value("${actors.rule.allow_system_mail_service}")
@Getter
private boolean allowSystemMailService;
@Getter @Setter private ActorRef appActor;
@Getter
@Setter
private ActorSystem actorSystem;
@Getter @Setter private ActorRef sessionManagerActor;
@Getter
@Setter
private ActorRef appActor;
@Getter @Setter private ActorRef statsActor;
@Getter
@Setter
private ActorRef sessionManagerActor;
@Getter private final Config config;
@Getter
@Setter
private ActorRef statsActor;
@Getter
private final Config config;
public ActorSystemContext() {
config = ConfigFactory.parseResources(AKKA_CONF_FILE_NAME).withFallback(ConfigFactory.load());
@ -187,7 +296,7 @@ public class ActorSystemContext {
eventService.save(event);
}
private String toString(Exception e) {
private String toString(Throwable e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
return sw.toString();
@ -207,4 +316,72 @@ public class ActorSystemContext {
private JsonNode toBodyJson(ServerAddress server, String method, String body) {
return mapper.createObjectNode().put("server", server.toString()).put("method", method).put("error", body);
}
public String getServerAddress() {
return discoveryService.getCurrentServer().getServerAddress().toString();
}
public void persistDebugInput(TenantId tenantId, EntityId entityId, TbMsg tbMsg, String relationType) {
persistDebugAsync(tenantId, entityId, "IN", tbMsg, relationType, null);
}
public void persistDebugInput(TenantId tenantId, EntityId entityId, TbMsg tbMsg, String relationType, Throwable error) {
persistDebugAsync(tenantId, entityId, "IN", tbMsg, relationType, error);
}
public void persistDebugOutput(TenantId tenantId, EntityId entityId, TbMsg tbMsg, String relationType, Throwable error) {
persistDebugAsync(tenantId, entityId, "OUT", tbMsg, relationType, error);
}
public void persistDebugOutput(TenantId tenantId, EntityId entityId, TbMsg tbMsg, String relationType) {
persistDebugAsync(tenantId, entityId, "OUT", tbMsg, relationType, null);
}
private void persistDebugAsync(TenantId tenantId, EntityId entityId, String type, TbMsg tbMsg, String relationType, Throwable error) {
try {
Event event = new Event();
event.setTenantId(tenantId);
event.setEntityId(entityId);
event.setType(DataConstants.DEBUG_RULE_NODE);
String metadata = mapper.writeValueAsString(tbMsg.getMetaData().getData());
ObjectNode node = mapper.createObjectNode()
.put("type", type)
.put("server", getServerAddress())
.put("entityId", tbMsg.getOriginator().getId().toString())
.put("entityName", tbMsg.getOriginator().getEntityType().name())
.put("msgId", tbMsg.getId().toString())
.put("msgType", tbMsg.getType())
.put("dataType", tbMsg.getDataType().name())
.put("relationType", relationType)
.put("data", tbMsg.getData())
.put("metadata", metadata);
if (error != null) {
node = node.put("error", toString(error));
}
event.setBody(node);
ListenableFuture<Event> future = eventService.saveAsync(event);
Futures.addCallback(future, new FutureCallback<Event>() {
@Override
public void onSuccess(@Nullable Event event) {
}
@Override
public void onFailure(Throwable th) {
log.error("Could not save debug Event for Node", th);
}
});
} catch (IOException ex) {
log.warn("Failed to persist rule node debug message", ex);
}
}
public static Exception toException(Throwable error) {
return Exception.class.isInstance(error) ? (Exception) error : new Exception(error);
}
}

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

@ -15,55 +15,51 @@
*/
package org.thingsboard.server.actors.app;
import akka.actor.*;
import akka.actor.ActorRef;
import akka.actor.LocalActorRef;
import akka.actor.OneForOneStrategy;
import akka.actor.Props;
import akka.actor.SupervisorStrategy;
import akka.actor.SupervisorStrategy.Directive;
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.plugin.PluginTerminationMsg;
import org.thingsboard.server.actors.service.ContextAwareActor;
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.plugin.PluginManager;
import org.thingsboard.server.actors.shared.plugin.SystemPluginManager;
import org.thingsboard.server.actors.shared.rule.RuleManager;
import org.thingsboard.server.actors.shared.rule.SystemRuleManager;
import org.thingsboard.server.actors.tenant.RuleChainDeviceMsg;
import org.thingsboard.server.actors.shared.rulechain.SystemRuleChainManager;
import org.thingsboard.server.actors.tenant.TenantActor;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.PluginId;
import org.thingsboard.server.common.data.id.RuleId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
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;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
import org.thingsboard.server.extensions.api.plugins.msg.ToPluginActorMsg;
import org.thingsboard.server.extensions.api.rules.ToRuleActorMsg;
import scala.concurrent.duration.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
public class AppActor extends ContextAwareActor {
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 final RuleManager ruleManager;
private final PluginManager pluginManager;
private final TenantService tenantService;
private final Map<TenantId, ActorRef> tenantActors;
private AppActor(ActorSystemContext systemContext) {
super(systemContext);
this.ruleManager = new SystemRuleManager(systemContext);
this.pluginManager = new SystemPluginManager(systemContext);
super(systemContext, new SystemRuleChainManager(systemContext));
this.tenantService = systemContext.getTenantService();
this.tenantActors = new HashMap<>();
}
@ -77,8 +73,7 @@ public class AppActor extends ContextAwareActor {
public void preStart() {
logger.info("Starting main system actor.");
try {
ruleManager.init(this.context());
pluginManager.init(this.context());
initRuleChains();
if (systemContext.isTenantComponentsInitEnabled()) {
PageDataIterable<Tenant> tenantIterator = new PageDataIterable<>(tenantService::findTenants, ENTITY_PACK_LIMIT);
@ -96,93 +91,89 @@ public class AppActor extends ContextAwareActor {
}
@Override
public void onReceive(Object msg) throws Exception {
logger.debug("Received message: {}", msg);
if (msg instanceof ToDeviceActorMsg) {
processDeviceMsg((ToDeviceActorMsg) msg);
} else if (msg instanceof ToPluginActorMsg) {
onToPluginMsg((ToPluginActorMsg) msg);
} else if (msg instanceof ToRuleActorMsg) {
onToRuleMsg((ToRuleActorMsg) msg);
} else if (msg instanceof ToDeviceActorNotificationMsg) {
onToDeviceActorMsg((ToDeviceActorNotificationMsg) msg);
} else if (msg instanceof Terminated) {
processTermination((Terminated) msg);
} else if (msg instanceof ClusterEventMsg) {
broadcast(msg);
} else if (msg instanceof ComponentLifecycleMsg) {
onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
} else if (msg instanceof PluginTerminationMsg) {
onPluginTerminated((PluginTerminationMsg) msg);
} else {
logger.warning("Unknown message: {}!", msg);
protected boolean process(TbActorMsg msg) {
switch (msg.getMsgType()) {
case SEND_TO_CLUSTER_MSG:
onPossibleClusterMsg((SendToClusterMsg) msg);
break;
case CLUSTER_EVENT_MSG:
broadcast(msg);
break;
case COMPONENT_LIFE_CYCLE_MSG:
onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
break;
case SERVICE_TO_RULE_ENGINE_MSG:
onServiceToRuleEngineMsg((ServiceToRuleEngineMsg) msg);
break;
case DEVICE_SESSION_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:
case DEVICE_RPC_REQUEST_TO_DEVICE_ACTOR_MSG:
case SERVER_RPC_RESPONSE_TO_DEVICE_ACTOR_MSG:
onToDeviceActorMsg((TenantAwareMsg) msg);
break;
case ACTOR_SYSTEM_TO_DEVICE_SESSION_ACTOR_MSG:
onToDeviceSessionMsg((BasicActorSystemToDeviceSessionActorMsg) msg);
default:
return false;
}
return true;
}
private void onPluginTerminated(PluginTerminationMsg msg) {
pluginManager.remove(msg.getId());
private void onToDeviceSessionMsg(BasicActorSystemToDeviceSessionActorMsg msg) {
systemContext.getSessionManagerActor().tell(msg, self());
}
private void broadcast(Object msg) {
pluginManager.broadcast(msg);
tenantActors.values().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender()));
private void onPossibleClusterMsg(SendToClusterMsg msg) {
Optional<ServerAddress> address = systemContext.getRoutingService().resolveById(msg.getEntityId());
if (address.isPresent()) {
systemContext.getRpcService().tell(
systemContext.getEncodingService().convertToProtoDataMessage(address.get(), msg.getMsg()));
} else {
self().tell(msg.getMsg(), ActorRef.noSender());
}
}
private void onToRuleMsg(ToRuleActorMsg msg) {
ActorRef target;
private void onServiceToRuleEngineMsg(ServiceToRuleEngineMsg msg) {
if (SYSTEM_TENANT.equals(msg.getTenantId())) {
target = ruleManager.getOrCreateRuleActor(this.context(), msg.getRuleId());
//TODO: ashvayka handle this.
} else {
target = getOrCreateTenantActor(msg.getTenantId());
getOrCreateTenantActor(msg.getTenantId()).tell(msg, self());
}
target.tell(msg, ActorRef.noSender());
}
private void onToPluginMsg(ToPluginActorMsg msg) {
ActorRef target;
if (SYSTEM_TENANT.equals(msg.getPluginTenantId())) {
target = pluginManager.getOrCreatePluginActor(this.context(), msg.getPluginId());
} else {
target = getOrCreateTenantActor(msg.getPluginTenantId());
}
target.tell(msg, ActorRef.noSender());
@Override
protected void broadcast(Object msg) {
super.broadcast(msg);
tenantActors.values().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender()));
}
private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) {
ActorRef target = null;
ActorRef target;
if (SYSTEM_TENANT.equals(msg.getTenantId())) {
Optional<PluginId> pluginId = msg.getPluginId();
Optional<RuleId> ruleId = msg.getRuleId();
if (pluginId.isPresent()) {
target = pluginManager.getOrCreatePluginActor(this.context(), pluginId.get());
} else if (ruleId.isPresent()) {
Optional<ActorRef> ref = ruleManager.update(this.context(), ruleId.get(), msg.getEvent());
if (ref.isPresent()) {
target = ref.get();
} else {
logger.debug("Failed to find actor for rule: [{}]", ruleId);
return;
}
}
target = getEntityActorRef(msg.getEntityId());
} else {
target = getOrCreateTenantActor(msg.getTenantId());
}
if (target != null) {
target.tell(msg, ActorRef.noSender());
} else {
logger.debug("Invalid component lifecycle msg: {}", msg);
}
}
private void onToDeviceActorMsg(ToDeviceActorNotificationMsg msg) {
private void onToDeviceActorMsg(TenantAwareMsg msg) {
getOrCreateTenantActor(msg.getTenantId()).tell(msg, ActorRef.noSender());
}
private void processDeviceMsg(ToDeviceActorMsg toDeviceActorMsg) {
TenantId tenantId = toDeviceActorMsg.getTenantId();
private void processDeviceMsg(DeviceToDeviceActorMsg deviceToDeviceActorMsg) {
TenantId tenantId = deviceToDeviceActorMsg.getTenantId();
ActorRef tenantActor = getOrCreateTenantActor(tenantId);
if (toDeviceActorMsg.getPayload().getMsgType().requiresRulesProcessing()) {
tenantActor.tell(new RuleChainDeviceMsg(toDeviceActorMsg, ruleManager.getRuleChain(this.context())), context().self());
if (deviceToDeviceActorMsg.getPayload().getMsgType().requiresRulesProcessing()) {
// tenantActor.tell(new RuleChainDeviceMsg(deviceToDeviceActorMsg, ruleManager.getRuleChain(this.context())), context().self());
} else {
tenantActor.tell(toDeviceActorMsg, context().self());
tenantActor.tell(deviceToDeviceActorMsg, context().self());
}
}

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

@ -17,61 +17,73 @@ 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.rule.RulesProcessedMsg;
import org.thingsboard.server.actors.service.ContextAwareActor;
import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.actors.tenant.RuleChainDeviceMsg;
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.ToDeviceActorMsg;
import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg;
import org.thingsboard.server.extensions.api.device.DeviceCredentialsUpdateNotificationMsg;
import org.thingsboard.server.extensions.api.device.DeviceNameOrTypeUpdateMsg;
import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
import org.thingsboard.server.extensions.api.plugins.msg.*;
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;
public class DeviceActor extends ContextAwareActor {
private final LoggingAdapter logger = Logging.getLogger(getContext().system(), this);
private final TenantId tenantId;
private final DeviceId deviceId;
private final DeviceActorMessageProcessor processor;
private DeviceActor(ActorSystemContext systemContext, TenantId tenantId, DeviceId deviceId) {
super(systemContext);
this.tenantId = tenantId;
this.deviceId = deviceId;
this.processor = new DeviceActorMessageProcessor(systemContext, logger, deviceId);
this.processor = new DeviceActorMessageProcessor(systemContext, logger, tenantId, deviceId);
}
@Override
public void onReceive(Object msg) throws Exception {
if (msg instanceof RuleChainDeviceMsg) {
processor.process(context(), (RuleChainDeviceMsg) msg);
} else if (msg instanceof RulesProcessedMsg) {
processor.onRulesProcessedMsg(context(), (RulesProcessedMsg) msg);
} else if (msg instanceof ToDeviceActorMsg) {
processor.process(context(), (ToDeviceActorMsg) msg);
} else if (msg instanceof ToDeviceActorNotificationMsg) {
if (msg instanceof DeviceAttributesEventNotificationMsg) {
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);
break;
case DEVICE_ATTRIBUTES_UPDATE_TO_DEVICE_ACTOR_MSG:
processor.processAttributesUpdate(context(), (DeviceAttributesEventNotificationMsg) msg);
} else if (msg instanceof ToDeviceRpcRequestPluginMsg) {
processor.processRpcRequest(context(), (ToDeviceRpcRequestPluginMsg) msg);
} else if (msg instanceof DeviceCredentialsUpdateNotificationMsg){
break;
case DEVICE_CREDENTIALS_UPDATE_TO_DEVICE_ACTOR_MSG:
processor.processCredentialsUpdate();
} else if (msg instanceof DeviceNameOrTypeUpdateMsg){
break;
case DEVICE_NAME_OR_TYPE_UPDATE_TO_DEVICE_ACTOR_MSG:
processor.processNameOrTypeUpdate((DeviceNameOrTypeUpdateMsg) msg);
}
} else if (msg instanceof TimeoutMsg) {
processor.processTimeout(context(), (TimeoutMsg) msg);
} else if (msg instanceof ClusterEventMsg) {
processor.processClusterEventMsg((ClusterEventMsg) msg);
} else {
logger.debug("[{}][{}] Unknown msg type.", tenantId, deviceId, msg.getClass().getName());
break;
case DEVICE_RPC_REQUEST_TO_DEVICE_ACTOR_MSG:
processor.processRpcRequest(context(), (ToDeviceRpcRequestActorMsg) msg);
break;
case SERVER_RPC_RESPONSE_TO_DEVICE_ACTOR_MSG:
processor.processToServerRPCResponse(context(), (ToServerRpcResponseActorMsg) msg);
break;
case DEVICE_ACTOR_SERVER_SIDE_RPC_TIMEOUT_MSG:
processor.processServerSideRpcTimeout(context(), (DeviceActorServerSideRpcTimeoutMsg) msg);
break;
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);
break;
default:
return false;
}
return true;
}
public static class ActorCreator extends ContextBasedCreator<DeviceActor> {

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

@ -18,37 +18,75 @@ 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;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.thingsboard.rule.engine.api.RpcError;
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.rule.*;
import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor;
import org.thingsboard.server.actors.tenant.RuleChainDeviceMsg;
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;
import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
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.*;
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
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.MsgType;
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.extensions.api.device.*;
import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse;
import org.thingsboard.server.extensions.api.plugins.msg.RpcError;
import org.thingsboard.server.extensions.api.plugins.msg.TimeoutIntMsg;
import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg;
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequest;
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestBody;
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg;
import org.thingsboard.server.extensions.api.plugins.msg.ToPluginRpcResponseDeviceMsg;
import java.util.*;
import java.util.concurrent.ExecutionException;
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.FromDeviceRpcResponse;
import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg;
import org.thingsboard.server.service.rpc.ToServerRpcResponseActorMsg;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
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;
@ -59,46 +97,46 @@ import java.util.stream.Collectors;
*/
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;
private final Map<Integer, ToDeviceRpcRequestMetadata> toDeviceRpcPendingMap;
private final Map<Integer, ToServerRpcRequestMetadata> toServerRpcPendingMap;
private final Map<UUID, PendingSessionMsgData> pendingMsgs;
private final Map<Integer, ToDeviceRpcRequestMetadata> rpcPendingMap;
private final Gson gson = new Gson();
private final JsonParser jsonParser = new JsonParser();
private int rpcSeq = 0;
private String deviceName;
private String deviceType;
private DeviceAttributes deviceAttributes;
private TbMsgMetaData defaultMetaData;
public DeviceActorMessageProcessor(ActorSystemContext systemContext, LoggingAdapter logger, DeviceId deviceId) {
public DeviceActorMessageProcessor(ActorSystemContext systemContext, LoggingAdapter logger, TenantId tenantId, DeviceId deviceId) {
super(systemContext, logger);
this.tenantId = tenantId;
this.deviceId = deviceId;
this.sessions = new HashMap<>();
this.sessions = new LinkedHashMap<>();
this.attributeSubscriptions = new HashMap<>();
this.rpcSubscriptions = new HashMap<>();
this.rpcPendingMap = new HashMap<>();
this.toDeviceRpcPendingMap = new HashMap<>();
this.toServerRpcPendingMap = new HashMap<>();
this.pendingMsgs = new HashMap<>();
initAttributes();
}
private void initAttributes() {
//TODO: add invalidation of deviceType cache.
Device device = systemContext.getDeviceService().findDeviceById(deviceId);
this.deviceName = device.getName();
this.deviceType = device.getType();
this.deviceAttributes = new DeviceAttributes(fetchAttributes(DataConstants.CLIENT_SCOPE),
fetchAttributes(DataConstants.SERVER_SCOPE), fetchAttributes(DataConstants.SHARED_SCOPE));
this.defaultMetaData = new TbMsgMetaData();
this.defaultMetaData.putValue("deviceName", deviceName);
this.defaultMetaData.putValue("deviceType", deviceType);
}
private void refreshAttributes(DeviceAttributesEventNotificationMsg msg) {
if (msg.isDeleted()) {
msg.getDeletedKeys().forEach(key -> deviceAttributes.remove(key));
} else {
deviceAttributes.update(msg.getScope(), msg.getValues());
}
}
void processRpcRequest(ActorContext context, ToDeviceRpcRequestPluginMsg msg) {
void processRpcRequest(ActorContext context, ToDeviceRpcRequestActorMsg msg) {
ToDeviceRpcRequest request = msg.getMsg();
ToDeviceRpcRequestBody body = request.getBody();
ToDeviceRpcRequestMsg rpcRequest = new ToDeviceRpcRequestMsg(
@ -116,7 +154,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
boolean sent = rpcSubscriptions.size() > 0;
Set<SessionId> syncSessionSet = new HashSet<>();
rpcSubscriptions.entrySet().forEach(sub -> {
ToDeviceSessionActorMsg response = new BasicToDeviceSessionActorMsg(rpcRequest, sub.getKey());
ActorSystemToDeviceSessionActorMsg response = new BasicActorSystemToDeviceSessionActorMsg(rpcRequest, sub.getKey());
sendMsgToSessionActor(response, sub.getValue().getServer());
if (SessionType.SYNC == sub.getValue().getType()) {
syncSessionSet.add(sub.getKey());
@ -125,9 +163,8 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
syncSessionSet.forEach(rpcSubscriptions::remove);
if (request.isOneway() && sent) {
ToPluginRpcResponseDeviceMsg responsePluginMsg = toPluginRpcResponseMsg(msg, (String) null);
context.parent().tell(responsePluginMsg, ActorRef.noSender());
logger.debug("[{}] Rpc command response sent [{}]!", deviceId, request.getId());
systemContext.getDeviceRpcService().processRpcResponseFromDevice(new FromDeviceRpcResponse(msg.getMsg().getId(), msg.getServerAddress(), null, null));
} else {
registerPendingRpcRequest(context, msg, sent, rpcRequest, timeout);
}
@ -139,24 +176,46 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
}
private void registerPendingRpcRequest(ActorContext context, ToDeviceRpcRequestPluginMsg msg, boolean sent, ToDeviceRpcRequestMsg rpcRequest, long timeout) {
rpcPendingMap.put(rpcRequest.getRequestId(), new ToDeviceRpcRequestMetadata(msg, sent));
TimeoutIntMsg timeoutMsg = new TimeoutIntMsg(rpcRequest.getRequestId(), timeout);
private void registerPendingRpcRequest(ActorContext context, ToDeviceRpcRequestActorMsg msg, boolean sent, ToDeviceRpcRequestMsg rpcRequest, long timeout) {
toDeviceRpcPendingMap.put(rpcRequest.getRequestId(), new ToDeviceRpcRequestMetadata(msg, sent));
DeviceActorServerSideRpcTimeoutMsg timeoutMsg = new DeviceActorServerSideRpcTimeoutMsg(rpcRequest.getRequestId(), timeout);
scheduleMsgWithDelay(context, timeoutMsg, timeoutMsg.getTimeout());
}
public void processTimeout(ActorContext context, TimeoutMsg msg) {
ToDeviceRpcRequestMetadata requestMd = rpcPendingMap.remove(msg.getId());
void processServerSideRpcTimeout(ActorContext context, DeviceActorServerSideRpcTimeoutMsg msg) {
ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(msg.getId());
if (requestMd != null) {
logger.debug("[{}] RPC request [{}] timeout detected!", deviceId, msg.getId());
ToPluginRpcResponseDeviceMsg responsePluginMsg = toPluginRpcResponseMsg(requestMd.getMsg(), requestMd.isSent() ? RpcError.TIMEOUT : RpcError.NO_ACTIVE_CONNECTION);
context.parent().tell(responsePluginMsg, ActorRef.noSender());
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());
}
}
}
private void sendPendingRequests(ActorContext context, SessionId sessionId, SessionType type, Optional<ServerAddress> server) {
if (!rpcPendingMap.isEmpty()) {
logger.debug("[{}] Pushing {} pending RPC messages to new async session [{}]", deviceId, rpcPendingMap.size(), 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);
rpcSubscriptions.remove(sessionId);
@ -166,41 +225,196 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
}
Set<Integer> sentOneWayIds = new HashSet<>();
if (type == SessionType.ASYNC) {
rpcPendingMap.entrySet().forEach(processPendingRpc(context, sessionId, server, sentOneWayIds));
toDeviceRpcPendingMap.entrySet().forEach(processPendingRpc(context, sessionId, server, sentOneWayIds));
} else {
rpcPendingMap.entrySet().stream().findFirst().ifPresent(processPendingRpc(context, sessionId, server, sentOneWayIds));
toDeviceRpcPendingMap.entrySet().stream().findFirst().ifPresent(processPendingRpc(context, sessionId, server, sentOneWayIds));
}
sentOneWayIds.forEach(rpcPendingMap::remove);
sentOneWayIds.forEach(toDeviceRpcPendingMap::remove);
}
private Consumer<Map.Entry<Integer, ToDeviceRpcRequestMetadata>> processPendingRpc(ActorContext context, SessionId sessionId, Optional<ServerAddress> server, 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());
ToPluginRpcResponseDeviceMsg responsePluginMsg = toPluginRpcResponseMsg(entry.getValue().getMsg(), (String) null);
context.parent().tell(responsePluginMsg, ActorRef.noSender());
systemContext.getDeviceRpcService().processRpcResponseFromDevice(new FromDeviceRpcResponse(request.getId(), requestActorMsg.getServerAddress(), null, null));
}
ToDeviceRpcRequestMsg rpcRequest = new ToDeviceRpcRequestMsg(
entry.getKey(),
body.getMethod(),
body.getParams()
);
ToDeviceSessionActorMsg response = new BasicToDeviceSessionActorMsg(rpcRequest, sessionId);
ActorSystemToDeviceSessionActorMsg response = new BasicActorSystemToDeviceSessionActorMsg(rpcRequest, sessionId);
sendMsgToSessionActor(response, server);
};
}
void process(ActorContext context, ToDeviceActorMsg msg) {
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;
}
}
}
private void reportActivity() {
systemContext.getDeviceStateService().onDeviceActivity(deviceId);
}
private void reportSessionOpen() {
systemContext.getDeviceStateService().onDeviceConnect(deviceId);
}
private void reportSessionClose() {
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.getClientAttributeNames());
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());
}
@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);
}
}
});
}
private ListenableFuture<List<AttributeKvEntry>> getAttributeKvEntries(DeviceId deviceId, String scope, Optional<Set<String>> names) {
if (names.isPresent()) {
if (!names.get().isEmpty()) {
return systemContext.getAttributesService().find(deviceId, scope, names.get());
} else {
return systemContext.getAttributesService().findAll(deviceId, scope);
}
} else {
return Futures.immediateFuture(Collections.emptyList());
}
}
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 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));
}
TbMsgMetaData metaData = defaultMetaData.copy();
metaData.putValue("ts", entry.getKey() + "");
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);
}
}
private void handleClientSideRPCRequest(ActorContext context, DeviceToDeviceActorMsg src) {
ToServerRpcRequestMsg request = (ToServerRpcRequestMsg) src.getPayload();
JsonObject json = new JsonObject();
json.addProperty("method", request.getMethod());
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);
scheduleMsgWithDelay(context, new DeviceActorClientSideRpcTimeoutMsg(request.getRequestId(), systemContext.getClientSideRpcTimeout()), systemContext.getClientSideRpcTimeout());
toServerRpcPendingMap.put(request.getRequestId(), new ToServerRpcRequestMetadata(src.getSessionId(), src.getSessionType(), src.getServerAddress()));
}
public 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());
}
}
void processToServerRPCResponse(ActorContext context, ToServerRpcResponseActorMsg msg) {
ToServerRpcRequestMetadata data = toServerRpcPendingMap.remove(msg.getMsg().getRequestId());
if (data != null) {
sendMsgToSessionActor(new BasicActorSystemToDeviceSessionActorMsg(msg.getMsg(), data.getSessionId()), data.getServer());
}
}
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());
}
context.parent().tell(new DeviceActorToRuleEngineMsg(context.self(), tbMsg), context.self());
}
void processAttributesUpdate(ActorContext context, DeviceAttributesEventNotificationMsg msg) {
refreshAttributes(msg);
if (attributeSubscriptions.size() > 0) {
ToDeviceMsg notification = null;
if (msg.isDeleted()) {
@ -221,7 +435,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
if (notification != null) {
ToDeviceMsg finalNotification = notification;
attributeSubscriptions.entrySet().forEach(sub -> {
ToDeviceSessionActorMsg response = new BasicToDeviceSessionActorMsg(finalNotification, sub.getKey());
ActorSystemToDeviceSessionActorMsg response = new BasicActorSystemToDeviceSessionActorMsg(finalNotification, sub.getKey());
sendMsgToSessionActor(response, sub.getValue().getServer());
});
}
@ -230,50 +444,30 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
}
}
void process(ActorContext context, RuleChainDeviceMsg srcMsg) {
ChainProcessingMetaData md = new ChainProcessingMetaData(srcMsg.getRuleChain(),
srcMsg.getToDeviceActorMsg(), new DeviceMetaData(deviceId, deviceName, deviceType, deviceAttributes), context.self());
ChainProcessingContext ctx = new ChainProcessingContext(md);
if (ctx.getChainLength() > 0) {
RuleProcessingMsg msg = new RuleProcessingMsg(ctx);
ActorRef ruleActorRef = ctx.getCurrentActor();
ruleActorRef.tell(msg, ActorRef.noSender());
} else {
context.self().tell(new RulesProcessedMsg(ctx), context.self());
}
}
void processRpcResponses(ActorContext context, ToDeviceActorMsg msg) {
private void processRpcResponses(ActorContext context, DeviceToDeviceActorMsg msg) {
SessionId sessionId = msg.getSessionId();
FromDeviceMsg inMsg = msg.getPayload();
if (inMsg.getMsgType() == MsgType.TO_DEVICE_RPC_RESPONSE) {
if (inMsg.getMsgType() == SessionMsgType.TO_DEVICE_RPC_RESPONSE) {
logger.debug("[{}] Processing rpc command response [{}]", deviceId, sessionId);
ToDeviceRpcResponseMsg responseMsg = (ToDeviceRpcResponseMsg) inMsg;
ToDeviceRpcRequestMetadata requestMd = rpcPendingMap.remove(responseMsg.getRequestId());
ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(responseMsg.getRequestId());
boolean success = requestMd != null;
if (success) {
ToPluginRpcResponseDeviceMsg responsePluginMsg = toPluginRpcResponseMsg(requestMd.getMsg(), responseMsg.getData());
Optional<ServerAddress> pluginServerAddress = requestMd.getMsg().getServerAddress();
if (pluginServerAddress.isPresent()) {
systemContext.getRpcService().tell(pluginServerAddress.get(), responsePluginMsg);
logger.debug("[{}] Rpc command response sent to remote plugin actor [{}]!", deviceId, requestMd.getMsg().getMsg().getId());
} else {
context.parent().tell(responsePluginMsg, ActorRef.noSender());
logger.debug("[{}] Rpc command response sent to local plugin actor [{}]!", deviceId, requestMd.getMsg().getMsg().getId());
}
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(MsgType.TO_DEVICE_RPC_REQUEST, responseMsg.getRequestId())
: BasicCommandAckResponse.onError(MsgType.TO_DEVICE_RPC_REQUEST, responseMsg.getRequestId(), new TimeoutException());
sendMsgToSessionActor(new BasicToDeviceSessionActorMsg(response, msg.getSessionId()), msg.getServerAddress());
? 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());
}
}
}
public void processClusterEventMsg(ClusterEventMsg msg) {
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()
@ -283,102 +477,79 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
}
}
private ToPluginRpcResponseDeviceMsg toPluginRpcResponseMsg(ToDeviceRpcRequestPluginMsg requestMsg, String data) {
return toPluginRpcResponseMsg(requestMsg, data, null);
}
private ToPluginRpcResponseDeviceMsg toPluginRpcResponseMsg(ToDeviceRpcRequestPluginMsg requestMsg, RpcError error) {
return toPluginRpcResponseMsg(requestMsg, null, error);
}
private ToPluginRpcResponseDeviceMsg toPluginRpcResponseMsg(ToDeviceRpcRequestPluginMsg requestMsg, String data, RpcError error) {
return new ToPluginRpcResponseDeviceMsg(
requestMsg.getPluginId(),
requestMsg.getPluginTenantId(),
new FromDeviceRpcResponse(requestMsg.getMsg().getId(),
data,
error
)
);
}
void onRulesProcessedMsg(ActorContext context, RulesProcessedMsg msg) {
ChainProcessingContext ctx = msg.getCtx();
ToDeviceActorMsg inMsg = ctx.getInMsg();
SessionId sid = inMsg.getSessionId();
ToDeviceSessionActorMsg response;
if (ctx.getResponse() != null) {
response = new BasicToDeviceSessionActorMsg(ctx.getResponse(), sid);
} else {
response = new BasicToDeviceSessionActorMsg(ctx.getError(), sid);
}
sendMsgToSessionActor(response, inMsg.getServerAddress());
}
private void processSubscriptionCommands(ActorContext context, ToDeviceActorMsg msg) {
private void processSubscriptionCommands(ActorContext context, DeviceToDeviceActorMsg msg) {
SessionId sessionId = msg.getSessionId();
SessionType sessionType = msg.getSessionType();
FromDeviceMsg inMsg = msg.getPayload();
if (inMsg.getMsgType() == MsgType.SUBSCRIBE_ATTRIBUTES_REQUEST) {
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() == MsgType.UNSUBSCRIBE_ATTRIBUTES_REQUEST) {
} else if (inMsg.getMsgType() == SessionMsgType.UNSUBSCRIBE_ATTRIBUTES_REQUEST) {
logger.debug("[{}] Canceling attributes subscription for session [{}]", deviceId, sessionId);
attributeSubscriptions.remove(sessionId);
} else if (inMsg.getMsgType() == MsgType.SUBSCRIBE_RPC_COMMANDS_REQUEST) {
} 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() == MsgType.UNSUBSCRIBE_RPC_COMMANDS_REQUEST) {
} else if (inMsg.getMsgType() == SessionMsgType.UNSUBSCRIBE_RPC_COMMANDS_REQUEST) {
logger.debug("[{}] Canceling rpc subscription for session [{}][{}]", deviceId, sessionId, sessionType);
rpcSubscriptions.remove(sessionId);
}
}
private void processSessionStateMsgs(ToDeviceActorMsg msg) {
private void processSessionStateMsgs(DeviceToDeviceActorMsg msg) {
SessionId sessionId = msg.getSessionId();
FromDeviceMsg inMsg = msg.getPayload();
if (inMsg instanceof SessionOpenMsg) {
logger.debug("[{}] Processing new session [{}]", deviceId, sessionId);
if (sessions.size() >= systemContext.getMaxConcurrentSessionsPerDevice()) {
SessionId sessionIdToRemove = sessions.keySet().stream().findFirst().orElse(null);
if (sessionIdToRemove != null) {
closeSession(sessionIdToRemove, sessions.remove(sessionIdToRemove));
}
}
sessions.put(sessionId, new SessionInfo(SessionType.ASYNC, msg.getServerAddress()));
if (sessions.size() == 1) {
reportSessionOpen();
}
} else if (inMsg instanceof SessionCloseMsg) {
logger.debug("[{}] Canceling subscriptions for closed session [{}]", deviceId, sessionId);
sessions.remove(sessionId);
attributeSubscriptions.remove(sessionId);
rpcSubscriptions.remove(sessionId);
if (sessions.isEmpty()) {
reportSessionClose();
}
}
}
private void sendMsgToSessionActor(ToDeviceSessionActorMsg response, Optional<ServerAddress> sessionAddress) {
private void sendMsgToSessionActor(ActorSystemToDeviceSessionActorMsg response, Optional<ServerAddress> sessionAddress) {
if (sessionAddress.isPresent()) {
ServerAddress address = sessionAddress.get();
logger.debug("{} Forwarding msg: {}", address, response);
systemContext.getRpcService().tell(sessionAddress.get(), response);
systemContext.getRpcService().tell(systemContext.getEncodingService()
.convertToProtoDataMessage(sessionAddress.get(), response));
} else {
systemContext.getSessionManagerActor().tell(response, ActorRef.noSender());
}
}
private List<AttributeKvEntry> fetchAttributes(String scope) {
try {
//TODO: replace this with async operation. Happens only during actor creation, but is still criticla for performance,
return systemContext.getAttributesService().findAll(this.deviceId, scope).get();
} catch (InterruptedException | ExecutionException e) {
logger.warning("[{}] Failed to fetch attributes for scope: {}", deviceId, scope);
throw new RuntimeException(e);
}
}
public void processCredentialsUpdate() {
sessions.forEach((k, v) -> {
sendMsgToSessionActor(new BasicToDeviceSessionActorMsg(new SessionCloseNotification(), k), v.getServer());
});
void processCredentialsUpdate() {
sessions.forEach(this::closeSession);
attributeSubscriptions.clear();
rpcSubscriptions.clear();
}
public void processNameOrTypeUpdate(DeviceNameOrTypeUpdateMsg msg) {
private void closeSession(SessionId sessionId, SessionInfo sessionInfo) {
sendMsgToSessionActor(new BasicActorSystemToDeviceSessionActorMsg(new SessionCloseNotification(), sessionId), sessionInfo.getServer());
}
void processNameOrTypeUpdate(DeviceNameOrTypeUpdateMsg msg) {
this.deviceName = msg.getDeviceName();
this.deviceType = msg.getDeviceType();
this.defaultMetaData = new TbMsgMetaData();
this.defaultMetaData.putValue("deviceName", deviceName);
this.defaultMetaData.putValue("deviceType", deviceType);
}
}

37
application/src/main/java/org/thingsboard/server/actors/device/DeviceActorToRuleEngineMsg.java

@ -0,0 +1,37 @@
/**
* 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 akka.actor.ActorRef;
import lombok.Data;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.TbMsg;
/**
* Created by ashvayka on 15.03.18.
*/
@Data
public final class DeviceActorToRuleEngineMsg implements TbActorMsg {
private final ActorRef callbackRef;
private final TbMsg tbMsg;
@Override
public MsgType getMsgType() {
return MsgType.DEVICE_ACTOR_TO_RULE_ENGINE_MSG;
}
}

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

@ -0,0 +1,40 @@
/**
* 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;
}

36
application/src/main/java/org/thingsboard/server/actors/device/RuleEngineQueuePutAckMsg.java

@ -0,0 +1,36 @@
/**
* 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.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.
*/
@Data
public final class RuleEngineQueuePutAckMsg implements TbActorMsg {
private final UUID id;
@Override
public MsgType getMsgType() {
return MsgType.RULE_ENGINE_QUEUE_PUT_ACK_MSG;
}
}

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

@ -16,7 +16,6 @@
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;

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

@ -16,13 +16,13 @@
package org.thingsboard.server.actors.device;
import lombok.Data;
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg;
import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg;
/**
* @author Andrew Shvayka
*/
@Data
public class ToDeviceRpcRequestMetadata {
private final ToDeviceRpcRequestPluginMsg msg;
private final ToDeviceRpcRequestActorMsg msg;
private final boolean sent;
}

24
extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/rpc/RpcMsg.java → application/src/main/java/org/thingsboard/server/actors/device/ToServerRpcRequestMetadata.java

@ -13,23 +13,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.extensions.api.plugins.rpc;
package org.thingsboard.server.actors.device;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
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 java.util.Optional;
/**
* @author Andrew Shvayka
*/
@ToString
@RequiredArgsConstructor
public class RpcMsg {
@Getter
private final ServerAddress serverAddress;
@Getter
private final int msgClazz;
@Getter
private final byte[] msgData;
@Data
public class ToServerRpcRequestMetadata {
private final SessionId sessionId;
private final SessionType type;
private final Optional<ServerAddress> server;
}

151
application/src/main/java/org/thingsboard/server/actors/plugin/PluginActor.java

@ -1,151 +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.plugin;
import akka.actor.ActorContext;
import akka.actor.ActorRef;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.service.ComponentActor;
import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.actors.stats.StatsPersistTick;
import org.thingsboard.server.common.data.id.PluginId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg;
import org.thingsboard.server.extensions.api.plugins.msg.ToPluginRpcResponseDeviceMsg;
import org.thingsboard.server.extensions.api.plugins.rest.PluginRestMsg;
import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg;
import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg;
import org.thingsboard.server.extensions.api.rules.RuleException;
public class PluginActor extends ComponentActor<PluginId, PluginActorMessageProcessor> {
private PluginActor(ActorSystemContext systemContext, TenantId tenantId, PluginId pluginId) {
super(systemContext, tenantId, pluginId);
setProcessor(new PluginActorMessageProcessor(tenantId, pluginId, systemContext,
logger, context().parent(), context().self()));
}
@Override
public void onReceive(Object msg) throws Exception {
if (msg instanceof PluginWebsocketMsg) {
onWebsocketMsg((PluginWebsocketMsg<?>) msg);
} else if (msg instanceof PluginRestMsg) {
onRestMsg((PluginRestMsg) msg);
} else if (msg instanceof PluginCallbackMessage) {
onPluginCallback((PluginCallbackMessage) msg);
} else if (msg instanceof RuleToPluginMsgWrapper) {
onRuleToPluginMsg((RuleToPluginMsgWrapper) msg);
} else if (msg instanceof PluginRpcMsg) {
onRpcMsg((PluginRpcMsg) msg);
} else if (msg instanceof ClusterEventMsg) {
onClusterEventMsg((ClusterEventMsg) msg);
} else if (msg instanceof ComponentLifecycleMsg) {
onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
} else if (msg instanceof ToPluginRpcResponseDeviceMsg) {
onRpcResponse((ToPluginRpcResponseDeviceMsg) msg);
} else if (msg instanceof PluginTerminationMsg) {
logger.info("[{}][{}] Going to terminate plugin actor.", tenantId, id);
context().parent().tell(msg, ActorRef.noSender());
context().stop(self());
} else if (msg instanceof TimeoutMsg) {
onTimeoutMsg(context(), (TimeoutMsg) msg);
} else if (msg instanceof StatsPersistTick) {
onStatsPersistTick(id);
} else {
logger.debug("[{}][{}] Unknown msg type.", tenantId, id, msg.getClass().getName());
}
}
private void onPluginCallback(PluginCallbackMessage msg) {
try {
processor.onPluginCallbackMsg(msg);
} catch (Exception e) {
logAndPersist("onPluginCallbackMsg", e);
}
}
private void onTimeoutMsg(ActorContext context, TimeoutMsg msg) {
processor.onTimeoutMsg(context, msg);
}
private void onRpcResponse(ToPluginRpcResponseDeviceMsg msg) {
processor.onDeviceRpcMsg(msg.getResponse());
}
private void onRuleToPluginMsg(RuleToPluginMsgWrapper msg) throws RuleException {
logger.debug("[{}] Going to process rule msg: {}", id, msg.getMsg());
try {
processor.onRuleToPluginMsg(msg);
increaseMessagesProcessedCount();
} catch (Exception e) {
logAndPersist("onRuleMsg", e);
}
}
private void onWebsocketMsg(PluginWebsocketMsg<?> msg) {
logger.debug("[{}] Going to process web socket msg: {}", id, msg);
try {
processor.onWebsocketMsg(msg);
increaseMessagesProcessedCount();
} catch (Exception e) {
logAndPersist("onWebsocketMsg", e);
}
}
private void onRestMsg(PluginRestMsg msg) {
logger.debug("[{}] Going to process rest msg: {}", id, msg);
try {
processor.onRestMsg(msg);
increaseMessagesProcessedCount();
} catch (Exception e) {
logAndPersist("onRestMsg", e);
}
}
private void onRpcMsg(PluginRpcMsg msg) {
try {
logger.debug("[{}] Going to process rpc msg: {}", id, msg);
processor.onRpcMsg(msg);
} catch (Exception e) {
logAndPersist("onRpcMsg", e);
}
}
public static class ActorCreator extends ContextBasedCreator<PluginActor> {
private static final long serialVersionUID = 1L;
private final TenantId tenantId;
private final PluginId pluginId;
public ActorCreator(ActorSystemContext context, TenantId tenantId, PluginId pluginId) {
super(context);
this.tenantId = tenantId;
this.pluginId = pluginId;
}
@Override
public PluginActor create() throws Exception {
return new PluginActor(context, tenantId, pluginId);
}
}
@Override
protected long getErrorPersistFrequency() {
return systemContext.getPluginErrorPersistFrequency();
}
}

251
application/src/main/java/org/thingsboard/server/actors/plugin/PluginActorMessageProcessor.java

@ -1,251 +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.plugin;
import akka.actor.ActorContext;
import akka.actor.ActorRef;
import akka.event.LoggingAdapter;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.shared.ComponentMsgProcessor;
import org.thingsboard.server.common.data.id.PluginId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.data.plugin.PluginMetaData;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.core.BasicStatusCodeResponse;
import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg;
import org.thingsboard.server.common.msg.session.MsgType;
import org.thingsboard.server.extensions.api.plugins.Plugin;
import org.thingsboard.server.extensions.api.plugins.PluginInitializationException;
import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse;
import org.thingsboard.server.extensions.api.plugins.msg.ResponsePluginToRuleMsg;
import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg;
import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg;
import org.thingsboard.server.extensions.api.plugins.rest.PluginRestMsg;
import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg;
import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg;
import org.thingsboard.server.extensions.api.rules.RuleException;
/**
* @author Andrew Shvayka
*/
public class PluginActorMessageProcessor extends ComponentMsgProcessor<PluginId> {
private final SharedPluginProcessingContext pluginCtx;
private final PluginProcessingContext trustedCtx;
private PluginMetaData pluginMd;
private Plugin pluginImpl;
private ComponentLifecycleState state;
protected PluginActorMessageProcessor(TenantId tenantId, PluginId pluginId, ActorSystemContext systemContext
, LoggingAdapter logger, ActorRef parent, ActorRef self) {
super(systemContext, logger, tenantId, pluginId);
this.pluginCtx = new SharedPluginProcessingContext(systemContext, tenantId, pluginId, parent, self);
this.trustedCtx = new PluginProcessingContext(pluginCtx, null);
}
@Override
public void start() throws Exception {
logger.info("[{}] Going to start plugin actor.", entityId);
pluginMd = systemContext.getPluginService().findPluginById(entityId);
if (pluginMd == null) {
throw new PluginInitializationException("Plugin not found!");
}
if (pluginMd.getConfiguration() == null) {
throw new PluginInitializationException("Plugin metadata is empty!");
}
state = pluginMd.getState();
if (state == ComponentLifecycleState.ACTIVE) {
logger.info("[{}] Plugin is active. Going to initialize plugin.", entityId);
initComponent();
} else {
logger.info("[{}] Plugin is suspended. Skipping plugin initialization.", entityId);
}
}
@Override
public void stop() throws Exception {
onStop();
}
private void initComponent() {
try {
pluginImpl = initComponent(pluginMd.getClazz(), ComponentType.PLUGIN, mapper.writeValueAsString(pluginMd.getConfiguration()));
} catch (InstantiationException e) {
throw new PluginInitializationException("No default constructor for plugin implementation!", e);
} catch (IllegalAccessException e) {
throw new PluginInitializationException("Illegal Access Exception during plugin initialization!", e);
} catch (ClassNotFoundException e) {
throw new PluginInitializationException("Plugin Class not found!", e);
} catch (JsonProcessingException e) {
throw new PluginInitializationException("Plugin Configuration is invalid!", e);
} catch (Exception e) {
throw new PluginInitializationException(e.getMessage(), e);
}
}
public void onRuleToPluginMsg(RuleToPluginMsgWrapper msg) throws RuleException {
if (state == ComponentLifecycleState.ACTIVE) {
try {
pluginImpl.process(trustedCtx, msg.getRuleTenantId(), msg.getRuleId(), msg.getMsg());
} catch (Exception ex) {
logger.debug("[{}] Failed to process RuleToPlugin msg: [{}] [{}]", tenantId, msg.getMsg(), ex);
RuleToPluginMsg ruleMsg = msg.getMsg();
MsgType responceMsgType = MsgType.RULE_ENGINE_ERROR;
Integer requestId = 0;
if (ruleMsg.getPayload() instanceof FromDeviceRequestMsg) {
requestId = ((FromDeviceRequestMsg) ruleMsg.getPayload()).getRequestId();
}
trustedCtx.reply(
new ResponsePluginToRuleMsg(ruleMsg.getUid(), tenantId, msg.getRuleId(),
BasicStatusCodeResponse.onError(responceMsgType, requestId, ex)));
}
} else {
//TODO: reply with plugin suspended message
}
}
public void onWebsocketMsg(PluginWebsocketMsg<?> msg) {
if (state == ComponentLifecycleState.ACTIVE) {
pluginImpl.process(new PluginProcessingContext(pluginCtx, msg.getSecurityCtx()), msg);
} else {
//TODO: reply with plugin suspended message
}
}
public void onRestMsg(PluginRestMsg msg) {
if (state == ComponentLifecycleState.ACTIVE) {
pluginImpl.process(new PluginProcessingContext(pluginCtx, msg.getSecurityCtx()), msg);
}
}
public void onRpcMsg(PluginRpcMsg msg) {
if (state == ComponentLifecycleState.ACTIVE) {
pluginImpl.process(trustedCtx, msg.getRpcMsg());
} else {
//TODO: reply with plugin suspended message
}
}
public void onPluginCallbackMsg(PluginCallbackMessage msg) {
if (state == ComponentLifecycleState.ACTIVE) {
if (msg.isSuccess()) {
msg.getCallback().onSuccess(trustedCtx, msg.getV());
} else {
msg.getCallback().onFailure(trustedCtx, msg.getE());
}
} else {
//TODO: reply with plugin suspended message
}
}
public void onTimeoutMsg(ActorContext context, TimeoutMsg<?> msg) {
if (state == ComponentLifecycleState.ACTIVE) {
pluginImpl.process(trustedCtx, msg);
}
}
public void onDeviceRpcMsg(FromDeviceRpcResponse response) {
if (state == ComponentLifecycleState.ACTIVE) {
pluginImpl.process(trustedCtx, response);
}
}
@Override
public void onClusterEventMsg(ClusterEventMsg msg) {
if (state == ComponentLifecycleState.ACTIVE) {
ServerAddress address = msg.getServerAddress();
if (msg.isAdded()) {
logger.debug("[{}] Going to process server add msg: {}", entityId, address);
pluginImpl.onServerAdded(trustedCtx, address);
} else {
logger.debug("[{}] Going to process server remove msg: {}", entityId, address);
pluginImpl.onServerRemoved(trustedCtx, address);
}
}
}
@Override
public void onCreated(ActorContext context) {
logger.info("[{}] Going to process onCreated plugin.", entityId);
}
@Override
public void onUpdate(ActorContext context) throws Exception {
PluginMetaData oldPluginMd = pluginMd;
pluginMd = systemContext.getPluginService().findPluginById(entityId);
boolean requiresRestart = false;
logger.info("[{}] Plugin configuration was updated from {} to {}.", entityId, oldPluginMd, pluginMd);
if (!oldPluginMd.getClazz().equals(pluginMd.getClazz())) {
logger.info("[{}] Plugin requires restart due to clazz change from {} to {}.",
entityId, oldPluginMd.getClazz(), pluginMd.getClazz());
requiresRestart = true;
} else if (!oldPluginMd.getConfiguration().equals(pluginMd.getConfiguration())) {
logger.info("[{}] Plugin requires restart due to configuration change from {} to {}.",
entityId, oldPluginMd.getConfiguration(), pluginMd.getConfiguration());
requiresRestart = true;
}
if (requiresRestart) {
this.state = ComponentLifecycleState.SUSPENDED;
if (pluginImpl != null) {
pluginImpl.stop(trustedCtx);
}
start();
}
}
@Override
public void onStop(ActorContext context) {
onStop();
scheduleMsgWithDelay(context, new PluginTerminationMsg(entityId), systemContext.getPluginActorTerminationDelay());
}
private void onStop() {
logger.info("[{}] Going to process onStop plugin.", entityId);
this.state = ComponentLifecycleState.SUSPENDED;
if (pluginImpl != null) {
pluginImpl.stop(trustedCtx);
}
}
@Override
public void onActivate(ActorContext context) throws Exception {
logger.info("[{}] Going to process onActivate plugin.", entityId);
this.state = ComponentLifecycleState.ACTIVE;
if (pluginImpl != null) {
pluginImpl.resume(trustedCtx);
logger.info("[{}] Plugin resumed.", entityId);
} else {
start();
}
}
@Override
public void onSuspend(ActorContext context) {
logger.info("[{}] Going to process onSuspend plugin.", entityId);
this.state = ComponentLifecycleState.SUSPENDED;
if (pluginImpl != null) {
pluginImpl.suspend(trustedCtx);
}
}
}

53
application/src/main/java/org/thingsboard/server/actors/plugin/PluginCallbackMessage.java

@ -1,53 +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.plugin;
import lombok.Data;
import lombok.Getter;
import lombok.ToString;
import org.thingsboard.server.extensions.api.plugins.PluginCallback;
import java.util.Optional;
/**
* @author Andrew Shvayka
*/
@ToString
public final class PluginCallbackMessage<V> {
@Getter
private final PluginCallback<V> callback;
@Getter
private final boolean success;
@Getter
private final V v;
@Getter
private final Exception e;
public static <V> PluginCallbackMessage<V> onSuccess(PluginCallback<V> callback, V data) {
return new PluginCallbackMessage<V>(true, callback, data, null);
}
public static <V> PluginCallbackMessage<V> onError(PluginCallback<V> callback, Exception e) {
return new PluginCallbackMessage<V>(false, callback, null, e);
}
private PluginCallbackMessage(boolean success, PluginCallback<V> callback, V v, Exception e) {
this.success = success;
this.callback = callback;
this.v = v;
this.e = e;
}
}

546
application/src/main/java/org/thingsboard/server/actors/plugin/PluginProcessingContext.java

@ -1,546 +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.plugin;
import akka.actor.ActorRef;
import com.google.common.base.Function;
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.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.id.*;
import org.thingsboard.server.common.data.kv.AttributeKey;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.kv.TsKvQuery;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.plugin.PluginMetaData;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rule.RuleMetaData;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg;
import org.thingsboard.server.extensions.api.plugins.PluginApiCallSecurityContext;
import org.thingsboard.server.extensions.api.plugins.PluginCallback;
import org.thingsboard.server.extensions.api.plugins.PluginContext;
import org.thingsboard.server.extensions.api.plugins.msg.*;
import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg;
import org.thingsboard.server.extensions.api.plugins.rpc.RpcMsg;
import org.thingsboard.server.extensions.api.plugins.ws.PluginWebsocketSessionRef;
import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg;
import javax.annotation.Nullable;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
@Slf4j
public final class PluginProcessingContext implements PluginContext {
private static final Executor executor = Executors.newSingleThreadExecutor();
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!";
private final SharedPluginProcessingContext pluginCtx;
private final Optional<PluginApiCallSecurityContext> securityCtx;
public PluginProcessingContext(SharedPluginProcessingContext pluginCtx, PluginApiCallSecurityContext securityCtx) {
super();
this.pluginCtx = pluginCtx;
this.securityCtx = Optional.ofNullable(securityCtx);
}
public void persistError(String method, Exception e) {
pluginCtx.persistError(method, e);
}
@Override
public void sendPluginRpcMsg(RpcMsg msg) {
this.pluginCtx.rpcService.tell(new PluginRpcMsg(pluginCtx.tenantId, pluginCtx.pluginId, msg));
}
@Override
public void send(PluginWebsocketMsg<?> wsMsg) throws IOException {
pluginCtx.msgEndpoint.send(wsMsg);
}
@Override
public void close(PluginWebsocketSessionRef sessionRef) throws IOException {
pluginCtx.msgEndpoint.close(sessionRef);
}
@Override
public void saveAttributes(final TenantId tenantId, final EntityId entityId, final String scope, final List<AttributeKvEntry> attributes, final PluginCallback<Void> callback) {
validate(entityId, new ValidationCallback(callback, ctx -> {
ListenableFuture<List<Void>> futures = pluginCtx.attributesService.save(entityId, scope, attributes);
Futures.addCallback(futures, getListCallback(callback, v -> {
if (entityId.getEntityType() == EntityType.DEVICE) {
onDeviceAttributesChanged(tenantId, new DeviceId(entityId.getId()), scope, attributes);
}
return null;
}), executor);
}));
}
@Override
public void removeAttributes(final TenantId tenantId, final EntityId entityId, final String scope, final List<String> keys, final PluginCallback<Void> callback) {
validate(entityId, new ValidationCallback(callback, ctx -> {
ListenableFuture<List<Void>> futures = pluginCtx.attributesService.removeAll(entityId, scope, keys);
Futures.addCallback(futures, getCallback(callback, v -> null), executor);
if (entityId.getEntityType() == EntityType.DEVICE) {
onDeviceAttributesDeleted(tenantId, new DeviceId(entityId.getId()), keys.stream().map(key -> new AttributeKey(scope, key)).collect(Collectors.toSet()));
}
}));
}
@Override
public void loadAttribute(EntityId entityId, String attributeType, String attributeKey, final PluginCallback<Optional<AttributeKvEntry>> callback) {
validate(entityId, new ValidationCallback(callback, ctx -> {
ListenableFuture<Optional<AttributeKvEntry>> future = pluginCtx.attributesService.find(entityId, attributeType, attributeKey);
Futures.addCallback(future, getCallback(callback, v -> v), executor);
}));
}
@Override
public void loadAttributes(EntityId entityId, String attributeType, Collection<String> attributeKeys, final PluginCallback<List<AttributeKvEntry>> callback) {
validate(entityId, new ValidationCallback(callback, ctx -> {
ListenableFuture<List<AttributeKvEntry>> future = pluginCtx.attributesService.find(entityId, attributeType, attributeKeys);
Futures.addCallback(future, getCallback(callback, v -> v), executor);
}));
}
@Override
public void loadAttributes(EntityId entityId, String attributeType, PluginCallback<List<AttributeKvEntry>> callback) {
validate(entityId, new ValidationCallback(callback, ctx -> {
ListenableFuture<List<AttributeKvEntry>> future = pluginCtx.attributesService.findAll(entityId, attributeType);
Futures.addCallback(future, getCallback(callback, v -> v), executor);
}));
}
@Override
public void loadAttributes(final EntityId entityId, final Collection<String> attributeTypes, final PluginCallback<List<AttributeKvEntry>> callback) {
validate(entityId, new ValidationCallback(callback, ctx -> {
List<ListenableFuture<List<AttributeKvEntry>>> futures = new ArrayList<>();
attributeTypes.forEach(attributeType -> futures.add(pluginCtx.attributesService.findAll(entityId, attributeType)));
convertFuturesAndAddCallback(callback, futures);
}));
}
@Override
public void loadAttributes(final EntityId entityId, final Collection<String> attributeTypes, final Collection<String> attributeKeys, final PluginCallback<List<AttributeKvEntry>> callback) {
validate(entityId, new ValidationCallback(callback, ctx -> {
List<ListenableFuture<List<AttributeKvEntry>>> futures = new ArrayList<>();
attributeTypes.forEach(attributeType -> futures.add(pluginCtx.attributesService.find(entityId, attributeType, attributeKeys)));
convertFuturesAndAddCallback(callback, futures);
}));
}
@Override
public void saveTsData(final EntityId entityId, final TsKvEntry entry, final PluginCallback<Void> callback) {
validate(entityId, new ValidationCallback(callback, ctx -> {
ListenableFuture<List<Void>> rsListFuture = pluginCtx.tsService.save(entityId, entry);
Futures.addCallback(rsListFuture, getListCallback(callback, v -> null), executor);
}));
}
@Override
public void saveTsData(final EntityId entityId, final List<TsKvEntry> entries, final PluginCallback<Void> callback) {
saveTsData(entityId, entries, 0L, callback);
}
@Override
public void saveTsData(final EntityId entityId, final List<TsKvEntry> entries, long ttl, final PluginCallback<Void> callback) {
validate(entityId, new ValidationCallback(callback, ctx -> {
ListenableFuture<List<Void>> rsListFuture = pluginCtx.tsService.save(entityId, entries, ttl);
Futures.addCallback(rsListFuture, getListCallback(callback, v -> null), executor);
}));
}
@Override
public void loadTimeseries(final EntityId entityId, final List<TsKvQuery> queries, final PluginCallback<List<TsKvEntry>> callback) {
validate(entityId, new ValidationCallback(callback, ctx -> {
ListenableFuture<List<TsKvEntry>> future = pluginCtx.tsService.findAll(entityId, queries);
Futures.addCallback(future, getCallback(callback, v -> v), executor);
}));
}
@Override
public void loadLatestTimeseries(final EntityId entityId, final PluginCallback<List<TsKvEntry>> callback) {
validate(entityId, new ValidationCallback(callback, ctx -> {
ListenableFuture<List<TsKvEntry>> future = pluginCtx.tsService.findAllLatest(entityId);
Futures.addCallback(future, getCallback(callback, v -> v), executor);
}));
}
@Override
public void logAttributesUpdated(PluginApiCallSecurityContext ctx, EntityId entityId, String attributeType,
List<AttributeKvEntry> attributes, Exception e) {
pluginCtx.auditLogService.logEntityAction(
ctx.getTenantId(),
ctx.getCustomerId(),
ctx.getUserId(),
ctx.getUserName(),
(UUIDBased & EntityId)entityId,
null,
ActionType.ATTRIBUTES_UPDATED,
e,
attributeType,
attributes);
}
@Override
public void logAttributesDeleted(PluginApiCallSecurityContext ctx, EntityId entityId, String attributeType, List<String> keys, Exception e) {
pluginCtx.auditLogService.logEntityAction(
ctx.getTenantId(),
ctx.getCustomerId(),
ctx.getUserId(),
ctx.getUserName(),
(UUIDBased & EntityId)entityId,
null,
ActionType.ATTRIBUTES_DELETED,
e,
attributeType,
keys);
}
@Override
public void logAttributesRead(PluginApiCallSecurityContext ctx, EntityId entityId, String attributeType, List<String> keys, Exception e) {
pluginCtx.auditLogService.logEntityAction(
ctx.getTenantId(),
ctx.getCustomerId(),
ctx.getUserId(),
ctx.getUserName(),
(UUIDBased & EntityId)entityId,
null,
ActionType.ATTRIBUTES_READ,
e,
attributeType,
keys);
}
@Override
public void loadLatestTimeseries(final EntityId entityId, final Collection<String> keys, final PluginCallback<List<TsKvEntry>> callback) {
validate(entityId, new ValidationCallback(callback, ctx -> {
ListenableFuture<List<TsKvEntry>> rsListFuture = pluginCtx.tsService.findLatest(entityId, keys);
Futures.addCallback(rsListFuture, getCallback(callback, v -> v), executor);
}));
}
@Override
public void reply(PluginToRuleMsg<?> msg) {
pluginCtx.parentActor.tell(msg, ActorRef.noSender());
}
@Override
public PluginId getPluginId() {
return pluginCtx.pluginId;
}
@Override
public Optional<PluginApiCallSecurityContext> getSecurityCtx() {
return securityCtx;
}
private void onDeviceAttributesDeleted(TenantId tenantId, DeviceId deviceId, Set<AttributeKey> keys) {
pluginCtx.toDeviceActor(DeviceAttributesEventNotificationMsg.onDelete(tenantId, deviceId, keys));
}
private void onDeviceAttributesChanged(TenantId tenantId, DeviceId deviceId, String scope, List<AttributeKvEntry> values) {
pluginCtx.toDeviceActor(DeviceAttributesEventNotificationMsg.onUpdate(tenantId, deviceId, scope, values));
}
private <T, R> FutureCallback<List<T>> getListCallback(final PluginCallback<R> callback, Function<List<T>, R> transformer) {
return new FutureCallback<List<T>>() {
@Override
public void onSuccess(@Nullable List<T> result) {
pluginCtx.self().tell(PluginCallbackMessage.onSuccess(callback, transformer.apply(result)), ActorRef.noSender());
}
@Override
public void onFailure(Throwable t) {
if (t instanceof Exception) {
pluginCtx.self().tell(PluginCallbackMessage.onError(callback, (Exception) t), ActorRef.noSender());
} else {
log.error("Critical error: {}", t.getMessage(), t);
}
}
};
}
private <T, R> FutureCallback<R> getCallback(final PluginCallback<T> callback, Function<R, T> transformer) {
return new FutureCallback<R>() {
@Override
public void onSuccess(@Nullable R result) {
try {
pluginCtx.self().tell(PluginCallbackMessage.onSuccess(callback, transformer.apply(result)), ActorRef.noSender());
} catch (Exception e) {
pluginCtx.self().tell(PluginCallbackMessage.onError(callback, e), ActorRef.noSender());
}
}
@Override
public void onFailure(Throwable t) {
if (t instanceof Exception) {
pluginCtx.self().tell(PluginCallbackMessage.onError(callback, (Exception) t), ActorRef.noSender());
} else {
log.error("Critical error: {}", t.getMessage(), t);
}
}
};
}
@Override
public void checkAccess(DeviceId deviceId, PluginCallback<Void> callback) {
validate(deviceId, new ValidationCallback(callback, ctx -> callback.onSuccess(ctx, null)));
}
private void validate(EntityId entityId, ValidationCallback callback) {
if (securityCtx.isPresent()) {
final PluginApiCallSecurityContext ctx = securityCtx.get();
switch (entityId.getEntityType()) {
case DEVICE:
validateDevice(ctx, entityId, callback);
return;
case ASSET:
validateAsset(ctx, entityId, callback);
return;
case RULE:
validateRule(ctx, entityId, callback);
return;
case PLUGIN:
validatePlugin(ctx, entityId, callback);
return;
case CUSTOMER:
validateCustomer(ctx, entityId, callback);
return;
case TENANT:
validateTenant(ctx, entityId, callback);
return;
default:
//TODO: add support of other entities
throw new IllegalStateException("Not Implemented!");
}
} else {
callback.onSuccess(this, ValidationResult.ok());
}
}
private void validateDevice(final PluginApiCallSecurityContext ctx, EntityId entityId, ValidationCallback callback) {
if (ctx.isSystemAdmin()) {
callback.onSuccess(this, ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
} else {
ListenableFuture<Device> deviceFuture = pluginCtx.deviceService.findDeviceByIdAsync(new DeviceId(entityId.getId()));
Futures.addCallback(deviceFuture, getCallback(callback, device -> {
if (device == null) {
return ValidationResult.entityNotFound(DEVICE_WITH_REQUESTED_ID_NOT_FOUND);
} else {
if (!device.getTenantId().equals(ctx.getTenantId())) {
return ValidationResult.accessDenied("Device doesn't belong to the current Tenant!");
} else if (ctx.isCustomerUser() && !device.getCustomerId().equals(ctx.getCustomerId())) {
return ValidationResult.accessDenied("Device doesn't belong to the current Customer!");
} else {
return ValidationResult.ok();
}
}
}));
}
}
private void validateAsset(final PluginApiCallSecurityContext ctx, EntityId entityId, ValidationCallback callback) {
if (ctx.isSystemAdmin()) {
callback.onSuccess(this, ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
} else {
ListenableFuture<Asset> assetFuture = pluginCtx.assetService.findAssetByIdAsync(new AssetId(entityId.getId()));
Futures.addCallback(assetFuture, getCallback(callback, asset -> {
if (asset == null) {
return ValidationResult.entityNotFound("Asset with requested id wasn't found!");
} else {
if (!asset.getTenantId().equals(ctx.getTenantId())) {
return ValidationResult.accessDenied("Asset doesn't belong to the current Tenant!");
} else if (ctx.isCustomerUser() && !asset.getCustomerId().equals(ctx.getCustomerId())) {
return ValidationResult.accessDenied("Asset doesn't belong to the current Customer!");
} else {
return ValidationResult.ok();
}
}
}));
}
}
private void validateRule(final PluginApiCallSecurityContext ctx, EntityId entityId, ValidationCallback callback) {
if (ctx.isCustomerUser()) {
callback.onSuccess(this, ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
} else {
ListenableFuture<RuleMetaData> ruleFuture = pluginCtx.ruleService.findRuleByIdAsync(new RuleId(entityId.getId()));
Futures.addCallback(ruleFuture, getCallback(callback, rule -> {
if (rule == null) {
return ValidationResult.entityNotFound("Rule with requested id wasn't found!");
} else {
if (ctx.isTenantAdmin() && !rule.getTenantId().equals(ctx.getTenantId())) {
return ValidationResult.accessDenied("Rule doesn't belong to the current Tenant!");
} else if (ctx.isSystemAdmin() && !rule.getTenantId().isNullUid()) {
return ValidationResult.accessDenied("Rule is not in system scope!");
} else {
return ValidationResult.ok();
}
}
}));
}
}
private void validatePlugin(final PluginApiCallSecurityContext ctx, EntityId entityId, ValidationCallback callback) {
if (ctx.isCustomerUser()) {
callback.onSuccess(this, ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
} else {
ListenableFuture<PluginMetaData> pluginFuture = pluginCtx.pluginService.findPluginByIdAsync(new PluginId(entityId.getId()));
Futures.addCallback(pluginFuture, getCallback(callback, plugin -> {
if (plugin == null) {
return ValidationResult.entityNotFound("Plugin with requested id wasn't found!");
} else {
if (ctx.isTenantAdmin() && !plugin.getTenantId().equals(ctx.getTenantId())) {
return ValidationResult.accessDenied("Plugin doesn't belong to the current Tenant!");
} else if (ctx.isSystemAdmin() && !plugin.getTenantId().isNullUid()) {
return ValidationResult.accessDenied("Plugin is not in system scope!");
} else {
return ValidationResult.ok();
}
}
}));
}
}
private void validateCustomer(final PluginApiCallSecurityContext ctx, EntityId entityId, ValidationCallback callback) {
if (ctx.isSystemAdmin()) {
callback.onSuccess(this, ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
} else {
ListenableFuture<Customer> customerFuture = pluginCtx.customerService.findCustomerByIdAsync(new CustomerId(entityId.getId()));
Futures.addCallback(customerFuture, getCallback(callback, customer -> {
if (customer == null) {
return ValidationResult.entityNotFound("Customer with requested id wasn't found!");
} else {
if (!customer.getTenantId().equals(ctx.getTenantId())) {
return ValidationResult.accessDenied("Customer doesn't belong to the current Tenant!");
} else if (ctx.isCustomerUser() && !customer.getId().equals(ctx.getCustomerId())) {
return ValidationResult.accessDenied("Customer doesn't relate to the currently authorized customer user!");
} else {
return ValidationResult.ok();
}
}
}));
}
}
private void validateTenant(final PluginApiCallSecurityContext ctx, EntityId entityId, ValidationCallback callback) {
if (ctx.isCustomerUser()) {
callback.onSuccess(this, ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
} else if (ctx.isSystemAdmin()) {
callback.onSuccess(this, ValidationResult.ok());
} else {
ListenableFuture<Tenant> tenantFuture = pluginCtx.tenantService.findTenantByIdAsync(new TenantId(entityId.getId()));
Futures.addCallback(tenantFuture, getCallback(callback, tenant -> {
if (tenant == null) {
return ValidationResult.entityNotFound("Tenant with requested id wasn't found!");
} else if (!tenant.getId().equals(ctx.getTenantId())) {
return ValidationResult.accessDenied("Tenant doesn't relate to the currently authorized user!");
} else {
return ValidationResult.ok();
}
}));
}
}
@Override
public ListenableFuture<List<EntityRelation>> findByFromAndType(EntityId from, String relationType) {
return this.pluginCtx.relationService.findByFromAndTypeAsync(from, relationType, RelationTypeGroup.COMMON);
}
@Override
public ListenableFuture<List<EntityRelation>> findByToAndType(EntityId from, String relationType) {
return this.pluginCtx.relationService.findByToAndTypeAsync(from, relationType, RelationTypeGroup.COMMON);
}
@Override
public Optional<ServerAddress> resolve(EntityId entityId) {
return pluginCtx.routingService.resolveById(entityId);
}
@Override
public void getDevice(DeviceId deviceId, PluginCallback<Device> callback) {
ListenableFuture<Device> deviceFuture = pluginCtx.deviceService.findDeviceByIdAsync(deviceId);
Futures.addCallback(deviceFuture, getCallback(callback, v -> v));
}
@Override
public void getCustomerDevices(TenantId tenantId, CustomerId customerId, int limit, PluginCallback<List<Device>> callback) {
//TODO: add caching here with async api.
List<Device> devices = pluginCtx.deviceService.findDevicesByTenantIdAndCustomerId(tenantId, customerId, new TextPageLink(limit)).getData();
pluginCtx.self().tell(PluginCallbackMessage.onSuccess(callback, devices), ActorRef.noSender());
}
@Override
public void sendRpcRequest(ToDeviceRpcRequest msg) {
pluginCtx.sendRpcRequest(msg);
}
@Override
public void logRpcRequest(PluginApiCallSecurityContext ctx, DeviceId deviceId, ToDeviceRpcRequestBody body, boolean oneWay, Optional<RpcError> rpcError, Exception e) {
String rpcErrorStr = "";
if (rpcError.isPresent()) {
rpcErrorStr = "RPC Error: " + rpcError.get().name();
}
String method = body.getMethod();
String params = body.getParams();
pluginCtx.auditLogService.logEntityAction(
ctx.getTenantId(),
ctx.getCustomerId(),
ctx.getUserId(),
ctx.getUserName(),
deviceId,
null,
ActionType.RPC_CALL,
e,
rpcErrorStr,
new Boolean(oneWay),
method,
params);
}
@Override
public void scheduleTimeoutMsg(TimeoutMsg msg) {
pluginCtx.scheduleTimeoutMsg(msg);
}
private void convertFuturesAndAddCallback(PluginCallback<List<AttributeKvEntry>> callback, List<ListenableFuture<List<AttributeKvEntry>>> futures) {
ListenableFuture<List<AttributeKvEntry>> future = Futures.transform(Futures.successfulAsList(futures),
(Function<? super List<List<AttributeKvEntry>>, ? extends List<AttributeKvEntry>>) input -> {
List<AttributeKvEntry> result = new ArrayList<>();
input.forEach(r -> result.addAll(r));
return result;
}, executor);
Futures.addCallback(future, getCallback(callback, v -> v), executor);
}
}

30
application/src/main/java/org/thingsboard/server/actors/plugin/PluginTerminationMsg.java

@ -1,30 +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.plugin;
import org.thingsboard.server.actors.shared.ActorTerminationMsg;
import org.thingsboard.server.common.data.id.PluginId;
import org.thingsboard.server.common.data.id.SessionId;
/**
* @author Andrew Shvayka
*/
public class PluginTerminationMsg extends ActorTerminationMsg<PluginId> {
public PluginTerminationMsg(PluginId id) {
super(id);
}
}

66
application/src/main/java/org/thingsboard/server/actors/plugin/RuleToPluginMsgWrapper.java

@ -1,66 +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.plugin;
import org.thingsboard.server.common.data.id.PluginId;
import org.thingsboard.server.common.data.id.RuleId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.aware.RuleAwareMsg;
import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg;
import org.thingsboard.server.extensions.api.plugins.msg.ToPluginActorMsg;
public class RuleToPluginMsgWrapper implements ToPluginActorMsg, RuleAwareMsg {
private final TenantId pluginTenantId;
private final PluginId pluginId;
private final TenantId ruleTenantId;
private final RuleId ruleId;
private final RuleToPluginMsg<?> msg;
public RuleToPluginMsgWrapper(TenantId pluginTenantId, PluginId pluginId, TenantId ruleTenantId, RuleId ruleId, RuleToPluginMsg<?> msg) {
super();
this.pluginTenantId = pluginTenantId;
this.pluginId = pluginId;
this.ruleTenantId = ruleTenantId;
this.ruleId = ruleId;
this.msg = msg;
}
@Override
public TenantId getPluginTenantId() {
return pluginTenantId;
}
@Override
public PluginId getPluginId() {
return pluginId;
}
public TenantId getRuleTenantId() {
return ruleTenantId;
}
@Override
public RuleId getRuleId() {
return ruleId;
}
public RuleToPluginMsg getMsg() {
return msg;
}
}

141
application/src/main/java/org/thingsboard/server/actors/plugin/SharedPluginProcessingContext.java

@ -1,141 +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.plugin;
import akka.actor.ActorRef;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.controller.plugin.PluginWebSocketMsgEndpoint;
import org.thingsboard.server.common.data.id.PluginId;
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.device.DeviceService;
import org.thingsboard.server.dao.plugin.PluginService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleService;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg;
import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg;
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequest;
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg;
import org.thingsboard.server.service.cluster.routing.ClusterRoutingService;
import org.thingsboard.server.service.cluster.rpc.ClusterRpcService;
import scala.concurrent.duration.Duration;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
@Slf4j
public final class SharedPluginProcessingContext {
final ActorRef parentActor;
final ActorRef currentActor;
final ActorSystemContext systemContext;
final PluginWebSocketMsgEndpoint msgEndpoint;
final AssetService assetService;
final DeviceService deviceService;
final RuleService ruleService;
final PluginService pluginService;
final CustomerService customerService;
final TenantService tenantService;
final TimeseriesService tsService;
final AttributesService attributesService;
final ClusterRpcService rpcService;
final ClusterRoutingService routingService;
final RelationService relationService;
final AuditLogService auditLogService;
final PluginId pluginId;
final TenantId tenantId;
public SharedPluginProcessingContext(ActorSystemContext sysContext, TenantId tenantId, PluginId pluginId,
ActorRef parentActor, ActorRef self) {
super();
this.tenantId = tenantId;
this.pluginId = pluginId;
this.parentActor = parentActor;
this.currentActor = self;
this.systemContext = sysContext;
this.msgEndpoint = sysContext.getWsMsgEndpoint();
this.tsService = sysContext.getTsService();
this.attributesService = sysContext.getAttributesService();
this.assetService = sysContext.getAssetService();
this.deviceService = sysContext.getDeviceService();
this.rpcService = sysContext.getRpcService();
this.routingService = sysContext.getRoutingService();
this.ruleService = sysContext.getRuleService();
this.pluginService = sysContext.getPluginService();
this.customerService = sysContext.getCustomerService();
this.tenantService = sysContext.getTenantService();
this.relationService = sysContext.getRelationService();
this.auditLogService = sysContext.getAuditLogService();
}
public PluginId getPluginId() {
return pluginId;
}
public TenantId getPluginTenantId() {
return tenantId;
}
public void toDeviceActor(DeviceAttributesEventNotificationMsg msg) {
forward(msg.getDeviceId(), msg, rpcService::tell);
}
public void sendRpcRequest(ToDeviceRpcRequest msg) {
log.trace("[{}] Forwarding msg {} to device actor!", pluginId, msg);
ToDeviceRpcRequestPluginMsg rpcMsg = new ToDeviceRpcRequestPluginMsg(pluginId, tenantId, msg);
forward(msg.getDeviceId(), rpcMsg, rpcService::tell);
}
private <T> void forward(DeviceId deviceId, T msg, BiConsumer<ServerAddress, T> rpcFunction) {
Optional<ServerAddress> instance = routingService.resolveById(deviceId);
if (instance.isPresent()) {
log.trace("[{}] Forwarding msg {} to remote device actor!", pluginId, msg);
rpcFunction.accept(instance.get(), msg);
} else {
log.trace("[{}] Forwarding msg {} to local device actor!", pluginId, msg);
parentActor.tell(msg, ActorRef.noSender());
}
}
public void scheduleTimeoutMsg(TimeoutMsg msg) {
log.debug("Scheduling msg {} with delay {} ms", msg, msg.getTimeout());
systemContext.getScheduler().scheduleOnce(
Duration.create(msg.getTimeout(), TimeUnit.MILLISECONDS),
currentActor,
msg,
systemContext.getActorSystem().dispatcher(),
ActorRef.noSender());
}
public void persistError(String method, Exception e) {
systemContext.persistError(tenantId, pluginId, method, e);
}
public ActorRef self() {
return currentActor;
}
}

48
application/src/main/java/org/thingsboard/server/actors/plugin/ValidationResult.java

@ -1,48 +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.plugin;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class ValidationResult {
private final ValidationResultCode resultCode;
private final String message;
public static ValidationResult ok() {
return new ValidationResult(ValidationResultCode.OK, "Ok");
}
public static ValidationResult accessDenied(String message) {
return new ValidationResult(ValidationResultCode.ACCESS_DENIED, message);
}
public static ValidationResult entityNotFound(String message) {
return new ValidationResult(ValidationResultCode.ENTITY_NOT_FOUND, message);
}
public static ValidationResult unauthorized(String message) {
return new ValidationResult(ValidationResultCode.UNAUTHORIZED, message);
}
public static ValidationResult internalError(String message) {
return new ValidationResult(ValidationResultCode.INTERNAL_ERROR, message);
}
}

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

@ -17,43 +17,23 @@ package org.thingsboard.server.actors.rpc;
import akka.actor.ActorRef;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.SerializationUtils;
import org.springframework.util.StringUtils;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.service.ActorService;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.PluginId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg;
import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg;
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
import org.thingsboard.server.extensions.api.plugins.msg.*;
import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg;
import org.thingsboard.server.extensions.api.plugins.rpc.RpcMsg;
import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
import org.thingsboard.server.service.cluster.rpc.GrpcSession;
import org.thingsboard.server.service.cluster.rpc.GrpcSessionListener;
import java.io.Serializable;
import java.util.UUID;
/**
* @author Andrew Shvayka
*/
@Slf4j
public class BasicRpcSessionListener implements GrpcSessionListener {
public static final String SESSION_RECEIVED_SESSION_ACTOR_MSG = "{} session [{}] received session actor msg {}";
private final ActorSystemContext context;
private final ActorService service;
private final ActorRef manager;
private final ActorRef self;
public BasicRpcSessionListener(ActorSystemContext context, ActorRef manager, ActorRef self) {
this.context = context;
this.service = context.getActorService();
public BasicRpcSessionListener(ActorService service, ActorRef manager, ActorRef self) {
this.service = service;
this.manager = manager;
this.self = self;
}
@ -73,47 +53,11 @@ public class BasicRpcSessionListener implements GrpcSessionListener {
}
@Override
public void onToPluginRpcMsg(GrpcSession session, ClusterAPIProtos.ToPluginRpcMessage msg) {
if (log.isTraceEnabled()) {
log.trace("{} session [{}] received plugin msg {}", getType(session), session.getRemoteServer(), msg);
}
service.onMsg(convert(session.getRemoteServer(), msg));
}
@Override
public void onToDeviceActorRpcMsg(GrpcSession session, ClusterAPIProtos.ToDeviceActorRpcMessage msg) {
log.trace("{} session [{}] received device actor msg {}", getType(session), session.getRemoteServer(), msg);
service.onMsg((ToDeviceActorMsg) deserialize(msg.getData().toByteArray()));
}
@Override
public void onToDeviceActorNotificationRpcMsg(GrpcSession session, ClusterAPIProtos.ToDeviceActorNotificationRpcMessage msg) {
log.trace("{} session [{}] received device actor notification msg {}", getType(session), session.getRemoteServer(), msg);
service.onMsg((ToDeviceActorNotificationMsg) deserialize(msg.getData().toByteArray()));
}
@Override
public void onToDeviceSessionActorRpcMsg(GrpcSession session, ClusterAPIProtos.ToDeviceSessionActorRpcMessage msg) {
log.trace(SESSION_RECEIVED_SESSION_ACTOR_MSG, getType(session), session.getRemoteServer(), msg);
service.onMsg((ToDeviceSessionActorMsg) deserialize(msg.getData().toByteArray()));
}
@Override
public void onToDeviceRpcRequestRpcMsg(GrpcSession session, ClusterAPIProtos.ToDeviceRpcRequestRpcMessage msg) {
log.trace(SESSION_RECEIVED_SESSION_ACTOR_MSG, getType(session), session.getRemoteServer(), msg);
service.onMsg(deserialize(session.getRemoteServer(), msg));
}
@Override
public void onFromDeviceRpcResponseRpcMsg(GrpcSession session, ClusterAPIProtos.ToPluginRpcResponseRpcMessage msg) {
log.trace(SESSION_RECEIVED_SESSION_ACTOR_MSG, getType(session), session.getRemoteServer(), msg);
service.onMsg(deserialize(session.getRemoteServer(), msg));
}
@Override
public void onToAllNodesRpcMessage(GrpcSession session, ClusterAPIProtos.ToAllNodesRpcMessage msg) {
log.trace(SESSION_RECEIVED_SESSION_ACTOR_MSG, getType(session), session.getRemoteServer(), msg);
service.onMsg((ToAllNodesMsg) deserialize(msg.getData().toByteArray()));
public void onReceiveClusterGrpcMsg(GrpcSession session, ClusterAPIProtos.ClusterMessage clusterMessage) {
log.trace("{} Service [{}] received session actor msg {}", getType(session),
session.getRemoteServer(),
clusterMessage);
service.onReceivedMsg(session.getRemoteServer(), clusterMessage);
}
@Override
@ -127,45 +71,5 @@ public class BasicRpcSessionListener implements GrpcSessionListener {
return session.isClient() ? "Client" : "Server";
}
private static PluginRpcMsg convert(ServerAddress serverAddress, ClusterAPIProtos.ToPluginRpcMessage msg) {
ClusterAPIProtos.PluginAddress address = msg.getAddress();
TenantId tenantId = new TenantId(toUUID(address.getTenantId()));
PluginId pluginId = new PluginId(toUUID(address.getPluginId()));
RpcMsg rpcMsg = new RpcMsg(serverAddress, msg.getClazz(), msg.getData().toByteArray());
return new PluginRpcMsg(tenantId, pluginId, rpcMsg);
}
private static UUID toUUID(ClusterAPIProtos.Uid uid) {
return new UUID(uid.getPluginUuidMsb(), uid.getPluginUuidLsb());
}
private static ToDeviceRpcRequestPluginMsg deserialize(ServerAddress serverAddress, ClusterAPIProtos.ToDeviceRpcRequestRpcMessage msg) {
ClusterAPIProtos.PluginAddress address = msg.getAddress();
TenantId pluginTenantId = new TenantId(toUUID(address.getTenantId()));
PluginId pluginId = new PluginId(toUUID(address.getPluginId()));
TenantId deviceTenantId = new TenantId(toUUID(msg.getDeviceTenantId()));
DeviceId deviceId = new DeviceId(toUUID(msg.getDeviceId()));
ToDeviceRpcRequestBody requestBody = new ToDeviceRpcRequestBody(msg.getMethod(), msg.getParams());
ToDeviceRpcRequest request = new ToDeviceRpcRequest(toUUID(msg.getMsgId()), null, deviceTenantId, deviceId, msg.getOneway(), msg.getExpTime(), requestBody);
return new ToDeviceRpcRequestPluginMsg(serverAddress, pluginId, pluginTenantId, request);
}
private static ToPluginRpcResponseDeviceMsg deserialize(ServerAddress serverAddress, ClusterAPIProtos.ToPluginRpcResponseRpcMessage msg) {
ClusterAPIProtos.PluginAddress address = msg.getAddress();
TenantId pluginTenantId = new TenantId(toUUID(address.getTenantId()));
PluginId pluginId = new PluginId(toUUID(address.getPluginId()));
RpcError error = !StringUtils.isEmpty(msg.getError()) ? RpcError.valueOf(msg.getError()) : null;
FromDeviceRpcResponse response = new FromDeviceRpcResponse(toUUID(msg.getMsgId()), msg.getResponse(), error);
return new ToPluginRpcResponseDeviceMsg(pluginId, pluginTenantId, response);
}
@SuppressWarnings("unchecked")
private static <T extends Serializable> T deserialize(byte[] data) {
return (T) SerializationUtils.deserialize(data);
}
}

2
application/src/main/java/org/thingsboard/server/actors/rpc/RpcBroadcastMsg.java

@ -23,5 +23,5 @@ import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
*/
@Data
public final class RpcBroadcastMsg {
private final ClusterAPIProtos.ToRpcServerMessage msg;
private final ClusterAPIProtos.ClusterMessage msg;
}

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

@ -23,12 +23,17 @@ 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.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
import org.thingsboard.server.service.cluster.discovery.ServerInstance;
import java.util.*;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.UUID;
/**
* @author Andrew Shvayka
@ -39,7 +44,7 @@ public class RpcManagerActor extends ContextAwareActor {
private final Map<ServerAddress, SessionActorInfo> sessionActors;
private final Map<ServerAddress, Queue<ClusterAPIProtos.ToRpcServerMessage>> pendingMsgs;
private final Map<ServerAddress, Queue<ClusterAPIProtos.ClusterMessage>> pendingMsgs;
private final ServerAddress instance;
@ -56,10 +61,16 @@ public class RpcManagerActor extends ContextAwareActor {
}
@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 RpcSessionTellMsg) {
onMsg((RpcSessionTellMsg) msg);
if (msg instanceof ClusterAPIProtos.ClusterMessage) {
onMsg((ClusterAPIProtos.ClusterMessage) msg);
} else if (msg instanceof RpcBroadcastMsg) {
onMsg((RpcBroadcastMsg) msg);
} else if (msg instanceof RpcSessionCreateRequestMsg) {
@ -77,24 +88,30 @@ public class RpcManagerActor extends ContextAwareActor {
private void onMsg(RpcBroadcastMsg msg) {
log.debug("Forwarding msg to session actors {}", msg);
sessionActors.keySet().forEach(address -> onMsg(new RpcSessionTellMsg(address, msg.getMsg())));
sessionActors.keySet().forEach(address -> onMsg(msg.getMsg()));
pendingMsgs.values().forEach(queue -> queue.add(msg.getMsg()));
}
private void onMsg(RpcSessionTellMsg msg) {
ServerAddress address = msg.getServerAddress();
SessionActorInfo session = sessionActors.get(address);
if (session != null) {
log.debug("{} Forwarding msg to session actor", address);
session.actor.tell(msg, ActorRef.noSender());
} else {
log.debug("{} Storing msg to pending queue", address);
Queue<ClusterAPIProtos.ToRpcServerMessage> queue = pendingMsgs.get(address);
if (queue == null) {
queue = new LinkedList<>();
pendingMsgs.put(address, queue);
private void onMsg(ClusterAPIProtos.ClusterMessage msg) {
if (msg.hasServerAddress()) {
ServerAddress address = new ServerAddress(msg.getServerAddress().getHost(),
msg.getServerAddress().getPort());
SessionActorInfo session = sessionActors.get(address);
if (session != null) {
log.debug("{} Forwarding msg to session actor", address);
session.getActor().tell(msg, ActorRef.noSender());
} else {
log.debug("{} Storing msg to pending queue", address);
Queue<ClusterAPIProtos.ClusterMessage> queue = pendingMsgs.get(address);
if (queue == null) {
queue = new LinkedList<>();
pendingMsgs.put(new ServerAddress(
msg.getServerAddress().getHost(), msg.getServerAddress().getPort()), queue);
}
queue.add(msg);
}
queue.add(msg.getMsg());
} else {
logger.warning("Cluster msg doesn't have set Server Address [{}]", msg);
}
}
@ -141,7 +158,7 @@ public class RpcManagerActor extends ContextAwareActor {
private void onSessionClose(boolean reconnect, ServerAddress remoteAddress) {
log.debug("[{}] session closed. Should reconnect: {}", remoteAddress, reconnect);
SessionActorInfo sessionRef = sessionActors.get(remoteAddress);
if (context().sender().equals(sessionRef.actor)) {
if (context().sender() != null && context().sender().equals(sessionRef.actor)) {
sessionActors.remove(remoteAddress);
pendingMsgs.remove(remoteAddress);
if (reconnect) {
@ -160,10 +177,10 @@ 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);
Queue<ClusterAPIProtos.ToRpcServerMessage> data = pendingMsgs.remove(remoteAddress);
Queue<ClusterAPIProtos.ClusterMessage> data = pendingMsgs.remove(remoteAddress);
if (data != null) {
log.debug("[{}][{}] Forwarding {} pending messages.", remoteAddress, uuid, data.size());
data.forEach(msg -> sender.tell(new RpcSessionTellMsg(remoteAddress, msg), ActorRef.noSender()));
data.forEach(msg -> sender.tell(new RpcSessionTellMsg(msg), ActorRef.noSender()));
} else {
log.debug("[{}][{}] No pending messages to forward.", remoteAddress, uuid);
}

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

@ -23,6 +23,7 @@ import io.grpc.stub.StreamObserver;
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.msg.TbActorMsg;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
import org.thingsboard.server.gen.cluster.ClusterRpcServiceGrpc;
@ -31,6 +32,8 @@ import org.thingsboard.server.service.cluster.rpc.GrpcSessionListener;
import java.util.UUID;
import static org.thingsboard.server.gen.cluster.ClusterAPIProtos.MessageType.CONNECT_RPC_MESSAGE;
/**
* @author Andrew Shvayka
*/
@ -47,17 +50,23 @@ public class RpcSessionActor extends ContextAwareActor {
this.sessionId = sessionId;
}
@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 RpcSessionTellMsg) {
tell((RpcSessionTellMsg) msg);
if (msg instanceof ClusterAPIProtos.ClusterMessage) {
tell((ClusterAPIProtos.ClusterMessage) msg);
} else if (msg instanceof RpcSessionCreateRequestMsg) {
initSession((RpcSessionCreateRequestMsg) msg);
}
}
private void tell(RpcSessionTellMsg msg) {
session.sendMsg(msg.getMsg());
private void tell(ClusterAPIProtos.ClusterMessage msg) {
session.sendMsg(msg);
}
@Override
@ -69,7 +78,7 @@ public class RpcSessionActor extends ContextAwareActor {
private void initSession(RpcSessionCreateRequestMsg msg) {
log.info("[{}] Initializing session", context().self());
ServerAddress remoteServer = msg.getRemoteAddress();
listener = new BasicRpcSessionListener(systemContext, context().parent(), context().self());
listener = new BasicRpcSessionListener(systemContext.getActorService(), context().parent(), context().self());
if (msg.getRemoteAddress() == null) {
// Server session
session = new GrpcSession(listener);
@ -84,7 +93,7 @@ public class RpcSessionActor extends ContextAwareActor {
session.initInputStream();
ClusterRpcServiceGrpc.ClusterRpcServiceStub stub = ClusterRpcServiceGrpc.newStub(channel);
StreamObserver<ClusterAPIProtos.ToRpcServerMessage> outputStream = stub.handlePluginMsgs(session.getInputStream());
StreamObserver<ClusterAPIProtos.ClusterMessage> outputStream = stub.handleMsgs(session.getInputStream());
session.setOutputStream(outputStream);
session.initOutputStream();
@ -108,11 +117,10 @@ public class RpcSessionActor extends ContextAwareActor {
}
}
private ClusterAPIProtos.ToRpcServerMessage toConnectMsg() {
private ClusterAPIProtos.ClusterMessage toConnectMsg() {
ServerAddress instance = systemContext.getDiscoveryService().getCurrentServer().getServerAddress();
return ClusterAPIProtos.ToRpcServerMessage.newBuilder().setConnectMsg(
ClusterAPIProtos.ConnectRpcMessage.newBuilder().setServerAddress(
ClusterAPIProtos.ServerAddress.newBuilder().setHost(instance.getHost()).setPort(instance.getPort()).build()).build()).build();
return ClusterAPIProtos.ClusterMessage.newBuilder().setMessageType(CONNECT_RPC_MESSAGE).setServerAddress(
ClusterAPIProtos.ServerAddress.newBuilder().setHost(instance.getHost())
.setPort(instance.getPort()).build()).build();
}
}

2
application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionCreateRequestMsg.java

@ -30,6 +30,6 @@ public final class RpcSessionCreateRequestMsg {
private final UUID msgUid;
private final ServerAddress remoteAddress;
private final StreamObserver<ClusterAPIProtos.ToRpcServerMessage> responseObserver;
private final StreamObserver<ClusterAPIProtos.ClusterMessage> responseObserver;
}

4
application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionTellMsg.java

@ -16,7 +16,6 @@
package org.thingsboard.server.actors.rpc;
import lombok.Data;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
/**
@ -24,6 +23,5 @@ import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
*/
@Data
public final class RpcSessionTellMsg {
private final ServerAddress serverAddress;
private final ClusterAPIProtos.ToRpcServerMessage msg;
private final ClusterAPIProtos.ClusterMessage msg;
}

117
application/src/main/java/org/thingsboard/server/actors/rule/ChainProcessingContext.java

@ -1,117 +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.rule;
import akka.actor.ActorRef;
import org.thingsboard.server.common.msg.core.RuleEngineError;
import org.thingsboard.server.common.msg.core.RuleEngineErrorMsg;
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
import org.thingsboard.server.common.msg.session.ToDeviceMsg;
import org.thingsboard.server.extensions.api.device.DeviceAttributes;
import org.thingsboard.server.extensions.api.device.DeviceMetaData;
public class ChainProcessingContext {
private final ChainProcessingMetaData md;
private final int index;
private final RuleEngineError error;
private ToDeviceMsg response;
public ChainProcessingContext(ChainProcessingMetaData md) {
super();
this.md = md;
this.index = 0;
this.error = RuleEngineError.NO_RULES;
}
private ChainProcessingContext(ChainProcessingContext other, int indexOffset, RuleEngineError error) {
super();
this.md = other.md;
this.index = other.index + indexOffset;
this.error = error;
this.response = other.response;
if (this.index < 0 || this.index >= this.md.chain.size()) {
throw new IllegalArgumentException("Can't apply offset " + indexOffset + " to the chain!");
}
}
public ActorRef getDeviceActor() {
return md.originator;
}
public ActorRef getCurrentActor() {
return md.chain.getRuleActorMd(index).getActorRef();
}
public boolean hasNext() {
return (getChainLength() - 1) > index;
}
public boolean isFailure() {
return (error != null && error.isCritical()) || (response != null && !response.isSuccess());
}
public ChainProcessingContext getNext() {
return new ChainProcessingContext(this, 1, this.error);
}
public ChainProcessingContext withError(RuleEngineError error) {
if (error != null && (this.error == null || this.error.getPriority() < error.getPriority())) {
return new ChainProcessingContext(this, 0, error);
} else {
return this;
}
}
public int getChainLength() {
return md.chain.size();
}
public ToDeviceActorMsg getInMsg() {
return md.inMsg;
}
public DeviceMetaData getDeviceMetaData() {
return md.deviceMetaData;
}
public String getDeviceName() {
return md.deviceMetaData.getDeviceName();
}
public String getDeviceType() {
return md.deviceMetaData.getDeviceType();
}
public DeviceAttributes getAttributes() {
return md.deviceMetaData.getDeviceAttributes();
}
public ToDeviceMsg getResponse() {
return response;
}
public void mergeResponse(ToDeviceMsg response) {
// TODO add merge logic
this.response = response;
}
public RuleEngineErrorMsg getError() {
return new RuleEngineErrorMsg(md.inMsg.getPayload().getMsgType(), error);
}
}

41
application/src/main/java/org/thingsboard/server/actors/rule/ChainProcessingMetaData.java

@ -1,41 +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.rule;
import akka.actor.ActorRef;
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
import org.thingsboard.server.extensions.api.device.DeviceMetaData;
/**
* Immutable part of chain processing data;
*
* @author ashvayka
*/
public final class ChainProcessingMetaData {
final RuleActorChain chain;
final ToDeviceActorMsg inMsg;
final ActorRef originator;
final DeviceMetaData deviceMetaData;
public ChainProcessingMetaData(RuleActorChain chain, ToDeviceActorMsg inMsg, DeviceMetaData deviceMetaData, ActorRef originator) {
super();
this.chain = chain;
this.inMsg = inMsg;
this.originator = originator;
this.deviceMetaData = deviceMetaData;
}
}

43
application/src/main/java/org/thingsboard/server/actors/rule/ComplexRuleActorChain.java

@ -1,43 +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.rule;
public class ComplexRuleActorChain implements RuleActorChain {
private final RuleActorChain systemChain;
private final RuleActorChain tenantChain;
public ComplexRuleActorChain(RuleActorChain systemChain, RuleActorChain tenantChain) {
super();
this.systemChain = systemChain;
this.tenantChain = tenantChain;
}
@Override
public int size() {
return systemChain.size() + tenantChain.size();
}
@Override
public RuleActorMetaData getRuleActorMd(int index) {
if (index < systemChain.size()) {
return systemChain.getRuleActorMd(index);
} else {
return tenantChain.getRuleActorMd(index - systemChain.size());
}
}
}

90
application/src/main/java/org/thingsboard/server/actors/rule/RuleActor.java

@ -1,90 +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.rule;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.service.ComponentActor;
import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.actors.stats.StatsPersistTick;
import org.thingsboard.server.common.data.id.RuleId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.extensions.api.plugins.msg.PluginToRuleMsg;
public class RuleActor extends ComponentActor<RuleId, RuleActorMessageProcessor> {
private RuleActor(ActorSystemContext systemContext, TenantId tenantId, RuleId ruleId) {
super(systemContext, tenantId, ruleId);
setProcessor(new RuleActorMessageProcessor(tenantId, ruleId, systemContext, logger));
}
@Override
public void onReceive(Object msg) throws Exception {
logger.debug("[{}] Received message: {}", id, msg);
if (msg instanceof RuleProcessingMsg) {
try {
processor.onRuleProcessingMsg(context(), (RuleProcessingMsg) msg);
increaseMessagesProcessedCount();
} catch (Exception e) {
logAndPersist("onDeviceMsg", e);
}
} else if (msg instanceof PluginToRuleMsg<?>) {
try {
processor.onPluginMsg(context(), (PluginToRuleMsg<?>) msg);
} catch (Exception e) {
logAndPersist("onPluginMsg", e);
}
} else if (msg instanceof ComponentLifecycleMsg) {
onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
} else if (msg instanceof ClusterEventMsg) {
onClusterEventMsg((ClusterEventMsg) msg);
} else if (msg instanceof RuleToPluginTimeoutMsg) {
try {
processor.onTimeoutMsg(context(), (RuleToPluginTimeoutMsg) msg);
} catch (Exception e) {
logAndPersist("onTimeoutMsg", e);
}
} else if (msg instanceof StatsPersistTick) {
onStatsPersistTick(id);
} else {
logger.debug("[{}][{}] Unknown msg type.", tenantId, id, msg.getClass().getName());
}
}
public static class ActorCreator extends ContextBasedCreator<RuleActor> {
private static final long serialVersionUID = 1L;
private final TenantId tenantId;
private final RuleId ruleId;
public ActorCreator(ActorSystemContext context, TenantId tenantId, RuleId ruleId) {
super(context);
this.tenantId = tenantId;
this.ruleId = ruleId;
}
@Override
public RuleActor create() throws Exception {
return new RuleActor(context, tenantId, ruleId);
}
}
@Override
protected long getErrorPersistFrequency() {
return systemContext.getRuleErrorPersistFrequency();
}
}

354
application/src/main/java/org/thingsboard/server/actors/rule/RuleActorMessageProcessor.java

@ -1,354 +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.rule;
import akka.actor.ActorContext;
import akka.actor.ActorRef;
import akka.event.LoggingAdapter;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.springframework.util.StringUtils;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.plugin.RuleToPluginMsgWrapper;
import org.thingsboard.server.actors.shared.ComponentMsgProcessor;
import org.thingsboard.server.common.data.id.PluginId;
import org.thingsboard.server.common.data.id.RuleId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
import org.thingsboard.server.common.data.plugin.PluginMetaData;
import org.thingsboard.server.common.data.rule.RuleMetaData;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.core.BasicStatusCodeResponse;
import org.thingsboard.server.common.msg.core.RuleEngineError;
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
import org.thingsboard.server.common.msg.session.ToDeviceMsg;
import org.thingsboard.server.extensions.api.plugins.PluginAction;
import org.thingsboard.server.extensions.api.plugins.msg.PluginToRuleMsg;
import org.thingsboard.server.extensions.api.plugins.msg.ResponsePluginToRuleMsg;
import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg;
import org.thingsboard.server.extensions.api.rules.*;
import java.util.*;
class RuleActorMessageProcessor extends ComponentMsgProcessor<RuleId> {
private final RuleProcessingContext ruleCtx;
private final Map<UUID, RuleProcessingMsg> pendingMsgMap;
private RuleMetaData ruleMd;
private ComponentLifecycleState state;
private List<RuleFilter> filters;
private RuleProcessor processor;
private PluginAction action;
private TenantId pluginTenantId;
private PluginId pluginId;
protected RuleActorMessageProcessor(TenantId tenantId, RuleId ruleId, ActorSystemContext systemContext, LoggingAdapter logger) {
super(systemContext, logger, tenantId, ruleId);
this.pendingMsgMap = new HashMap<>();
this.ruleCtx = new RuleProcessingContext(systemContext, ruleId);
}
@Override
public void start() throws Exception {
logger.info("[{}][{}] Starting rule actor.", entityId, tenantId);
ruleMd = systemContext.getRuleService().findRuleById(entityId);
if (ruleMd == null) {
throw new RuleInitializationException("Rule not found!");
}
state = ruleMd.getState();
if (state == ComponentLifecycleState.ACTIVE) {
logger.info("[{}] Rule is active. Going to initialize rule components.", entityId);
initComponent();
} else {
logger.info("[{}] Rule is suspended. Skipping rule components initialization.", entityId);
}
logger.info("[{}][{}] Started rule actor.", entityId, tenantId);
}
@Override
public void stop() throws Exception {
onStop();
}
private void initComponent() throws RuleException {
try {
if (!ruleMd.getFilters().isArray()) {
throw new RuntimeException("Filters are not array!");
}
fetchPluginInfo();
initFilters();
initProcessor();
initAction();
} catch (RuntimeException e) {
throw new RuleInitializationException("Unknown runtime exception!", e);
} catch (InstantiationException e) {
throw new RuleInitializationException("No default constructor for rule implementation!", e);
} catch (IllegalAccessException e) {
throw new RuleInitializationException("Illegal Access Exception during rule initialization!", e);
} catch (ClassNotFoundException e) {
throw new RuleInitializationException("Rule Class not found!", e);
} catch (Exception e) {
throw new RuleException(e.getMessage(), e);
}
}
private void initAction() throws Exception {
if (ruleMd.getAction() != null && !ruleMd.getAction().isNull()) {
action = initComponent(ruleMd.getAction());
}
}
private void initProcessor() throws Exception {
if (ruleMd.getProcessor() != null && !ruleMd.getProcessor().isNull()) {
processor = initComponent(ruleMd.getProcessor());
}
}
private void initFilters() throws Exception {
filters = new ArrayList<>(ruleMd.getFilters().size());
for (int i = 0; i < ruleMd.getFilters().size(); i++) {
filters.add(initComponent(ruleMd.getFilters().get(i)));
}
}
private void fetchPluginInfo() {
if (!StringUtils.isEmpty(ruleMd.getPluginToken())) {
PluginMetaData pluginMd = systemContext.getPluginService().findPluginByApiToken(ruleMd.getPluginToken());
pluginTenantId = pluginMd.getTenantId();
pluginId = pluginMd.getId();
}
}
protected void onRuleProcessingMsg(ActorContext context, RuleProcessingMsg msg) throws RuleException {
if (state != ComponentLifecycleState.ACTIVE) {
pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_ACTIVE_RULES);
return;
}
ChainProcessingContext chainCtx = msg.getCtx();
ToDeviceActorMsg inMsg = chainCtx.getInMsg();
ruleCtx.update(inMsg, chainCtx.getDeviceMetaData());
logger.debug("[{}] Going to filter in msg: {}", entityId, inMsg);
for (RuleFilter filter : filters) {
if (!filter.filter(ruleCtx, inMsg)) {
logger.debug("[{}] In msg is NOT valid for processing by current rule: {}", entityId, inMsg);
pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_FILTERS_MATCHED);
return;
}
}
RuleProcessingMetaData inMsgMd;
if (processor != null) {
logger.debug("[{}] Going to process in msg: {}", entityId, inMsg);
inMsgMd = processor.process(ruleCtx, inMsg);
} else {
inMsgMd = new RuleProcessingMetaData();
}
logger.debug("[{}] Going to convert in msg: {}", entityId, inMsg);
if (action != null) {
Optional<RuleToPluginMsg<?>> ruleToPluginMsgOptional = action.convert(ruleCtx, inMsg, inMsgMd);
if (ruleToPluginMsgOptional.isPresent()) {
RuleToPluginMsg<?> ruleToPluginMsg = ruleToPluginMsgOptional.get();
logger.debug("[{}] Device msg is converted to: {}", entityId, ruleToPluginMsg);
context.parent().tell(new RuleToPluginMsgWrapper(pluginTenantId, pluginId, tenantId, entityId, ruleToPluginMsg), context.self());
if (action.isOneWayAction()) {
pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_TWO_WAY_ACTIONS);
return;
} else {
pendingMsgMap.put(ruleToPluginMsg.getUid(), msg);
scheduleMsgWithDelay(context, new RuleToPluginTimeoutMsg(ruleToPluginMsg.getUid()), systemContext.getPluginProcessingTimeout());
return;
}
}
}
logger.debug("[{}] Nothing to send to plugin: {}", entityId, pluginId);
pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_TWO_WAY_ACTIONS);
}
void onPluginMsg(ActorContext context, PluginToRuleMsg<?> msg) {
RuleProcessingMsg pendingMsg = pendingMsgMap.remove(msg.getUid());
if (pendingMsg != null) {
ChainProcessingContext ctx = pendingMsg.getCtx();
if (isErrorResponce(msg)) {
pushToNextRule(context, ctx, RuleEngineError.NO_RESPONSE_FROM_ACTIONS);
} else {
Optional<ToDeviceMsg> ruleResponseOptional = action.convert(msg);
if (ruleResponseOptional.isPresent()) {
ctx.mergeResponse(ruleResponseOptional.get());
pushToNextRule(context, ctx, null);
} else {
pushToNextRule(context, ctx, RuleEngineError.NO_RESPONSE_FROM_ACTIONS);
}
}
} else {
logger.warning("[{}] Processing timeout detected: [{}]", entityId, msg.getUid());
}
}
private boolean isErrorResponce(PluginToRuleMsg<?> msg) {
if (msg instanceof ResponsePluginToRuleMsg) {
if (((ResponsePluginToRuleMsg) msg).getPayload() instanceof BasicStatusCodeResponse) {
BasicStatusCodeResponse responce = (BasicStatusCodeResponse) ((ResponsePluginToRuleMsg) msg).getPayload();
return !responce.isSuccess();
}
}
return false;
}
void onTimeoutMsg(ActorContext context, RuleToPluginTimeoutMsg msg) {
RuleProcessingMsg pendingMsg = pendingMsgMap.remove(msg.getMsgId());
if (pendingMsg != null) {
logger.debug("[{}] Processing timeout detected [{}]: {}", entityId, msg.getMsgId(), pendingMsg);
ChainProcessingContext ctx = pendingMsg.getCtx();
pushToNextRule(context, ctx, RuleEngineError.PLUGIN_TIMEOUT);
}
}
private void pushToNextRule(ActorContext context, ChainProcessingContext ctx, RuleEngineError error) {
if (error != null) {
ctx = ctx.withError(error);
}
if (ctx.isFailure()) {
logger.debug("[{}][{}] Forwarding processing chain to device actor due to failure.", ruleMd.getId(), ctx.getInMsg().getDeviceId());
ctx.getDeviceActor().tell(new RulesProcessedMsg(ctx), ActorRef.noSender());
} else if (!ctx.hasNext()) {
logger.debug("[{}][{}] Forwarding processing chain to device actor due to end of chain.", ruleMd.getId(), ctx.getInMsg().getDeviceId());
ctx.getDeviceActor().tell(new RulesProcessedMsg(ctx), ActorRef.noSender());
} else {
logger.debug("[{}][{}] Forwarding processing chain to next rule actor.", ruleMd.getId(), ctx.getInMsg().getDeviceId());
ChainProcessingContext nextTask = ctx.getNext();
nextTask.getCurrentActor().tell(new RuleProcessingMsg(nextTask), context.self());
}
}
@Override
public void onCreated(ActorContext context) {
logger.info("[{}] Going to process onCreated rule.", entityId);
}
@Override
public void onUpdate(ActorContext context) throws RuleException {
RuleMetaData oldRuleMd = ruleMd;
ruleMd = systemContext.getRuleService().findRuleById(entityId);
logger.info("[{}] Rule configuration was updated from {} to {}.", entityId, oldRuleMd, ruleMd);
try {
fetchPluginInfo();
if (filters == null || !Objects.equals(oldRuleMd.getFilters(), ruleMd.getFilters())) {
logger.info("[{}] Rule filters require restart due to json change from {} to {}.",
entityId, mapper.writeValueAsString(oldRuleMd.getFilters()), mapper.writeValueAsString(ruleMd.getFilters()));
stopFilters();
initFilters();
}
if (processor == null || !Objects.equals(oldRuleMd.getProcessor(), ruleMd.getProcessor())) {
logger.info("[{}] Rule processor require restart due to configuration change.", entityId);
stopProcessor();
initProcessor();
}
if (action == null || !Objects.equals(oldRuleMd.getAction(), ruleMd.getAction())) {
logger.info("[{}] Rule action require restart due to configuration change.", entityId);
stopAction();
initAction();
}
} catch (RuntimeException e) {
throw new RuleInitializationException("Unknown runtime exception!", e);
} catch (InstantiationException e) {
throw new RuleInitializationException("No default constructor for rule implementation!", e);
} catch (IllegalAccessException e) {
throw new RuleInitializationException("Illegal Access Exception during rule initialization!", e);
} catch (ClassNotFoundException e) {
throw new RuleInitializationException("Rule Class not found!", e);
} catch (JsonProcessingException e) {
throw new RuleInitializationException("Rule configuration is invalid!", e);
} catch (Exception e) {
throw new RuleInitializationException(e.getMessage(), e);
}
}
@Override
public void onActivate(ActorContext context) throws Exception {
logger.info("[{}] Going to process onActivate rule.", entityId);
this.state = ComponentLifecycleState.ACTIVE;
if (filters != null) {
filters.forEach(RuleLifecycleComponent::resume);
if (processor != null) {
processor.resume();
} else {
initProcessor();
}
if (action != null) {
action.resume();
}
logger.info("[{}] Rule resumed.", entityId);
} else {
start();
}
}
@Override
public void onSuspend(ActorContext context) {
logger.info("[{}] Going to process onSuspend rule.", entityId);
this.state = ComponentLifecycleState.SUSPENDED;
if (filters != null) {
filters.forEach(f -> f.suspend());
}
if (processor != null) {
processor.suspend();
}
if (action != null) {
action.suspend();
}
}
@Override
public void onStop(ActorContext context) {
logger.info("[{}] Going to process onStop rule.", entityId);
onStop();
scheduleMsgWithDelay(context, new RuleTerminationMsg(entityId), systemContext.getRuleActorTerminationDelay());
}
private void onStop() {
this.state = ComponentLifecycleState.SUSPENDED;
stopFilters();
stopProcessor();
stopAction();
}
@Override
public void onClusterEventMsg(ClusterEventMsg msg) throws Exception {
//Do nothing
}
private void stopAction() {
if (action != null) {
action.stop();
}
}
private void stopProcessor() {
if (processor != null) {
processor.stop();
}
}
private void stopFilters() {
if (filters != null) {
filters.forEach(f -> f.stop());
}
}
}

107
application/src/main/java/org/thingsboard/server/actors/rule/RuleActorMetaData.java

@ -1,107 +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.rule;
import java.util.Comparator;
import org.thingsboard.server.common.data.id.RuleId;
import akka.actor.ActorRef;
public class RuleActorMetaData {
private final RuleId ruleId;
private final boolean systemRule;
private final int weight;
private final ActorRef actorRef;
public static final Comparator<RuleActorMetaData> RULE_ACTOR_MD_COMPARATOR = new Comparator<RuleActorMetaData>() {
@Override
public int compare(RuleActorMetaData r1, RuleActorMetaData r2) {
if (r1.isSystemRule() && !r2.isSystemRule()) {
return 1;
} else if (!r1.isSystemRule() && r2.isSystemRule()) {
return -1;
} else {
return Integer.compare(r2.getWeight(), r1.getWeight());
}
}
};
public static RuleActorMetaData systemRule(RuleId ruleId, int weight, ActorRef actorRef) {
return new RuleActorMetaData(ruleId, true, weight, actorRef);
}
public static RuleActorMetaData tenantRule(RuleId ruleId, int weight, ActorRef actorRef) {
return new RuleActorMetaData(ruleId, false, weight, actorRef);
}
private RuleActorMetaData(RuleId ruleId, boolean systemRule, int weight, ActorRef actorRef) {
super();
this.ruleId = ruleId;
this.systemRule = systemRule;
this.weight = weight;
this.actorRef = actorRef;
}
public RuleId getRuleId() {
return ruleId;
}
public boolean isSystemRule() {
return systemRule;
}
public int getWeight() {
return weight;
}
public ActorRef getActorRef() {
return actorRef;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((ruleId == null) ? 0 : ruleId.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
RuleActorMetaData other = (RuleActorMetaData) obj;
if (ruleId == null) {
if (other.ruleId != null)
return false;
} else if (!ruleId.equals(other.ruleId))
return false;
return true;
}
@Override
public String toString() {
return "RuleActorMetaData [ruleId=" + ruleId + ", systemRule=" + systemRule + ", weight=" + weight + ", actorRef=" + actorRef + "]";
}
}

115
application/src/main/java/org/thingsboard/server/actors/rule/RuleProcessingContext.java

@ -1,115 +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.rule;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.Event;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmId;
import org.thingsboard.server.common.data.id.*;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
import org.thingsboard.server.extensions.api.device.DeviceMetaData;
import org.thingsboard.server.extensions.api.rules.RuleContext;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
public class RuleProcessingContext implements RuleContext {
private final TimeseriesService tsService;
private final EventService eventService;
private final AlarmService alarmService;
private final RuleId ruleId;
private TenantId tenantId;
private CustomerId customerId;
private DeviceId deviceId;
private DeviceMetaData deviceMetaData;
RuleProcessingContext(ActorSystemContext systemContext, RuleId ruleId) {
this.tsService = systemContext.getTsService();
this.eventService = systemContext.getEventService();
this.alarmService = systemContext.getAlarmService();
this.ruleId = ruleId;
}
void update(ToDeviceActorMsg toDeviceActorMsg, DeviceMetaData deviceMetaData) {
this.tenantId = toDeviceActorMsg.getTenantId();
this.customerId = toDeviceActorMsg.getCustomerId();
this.deviceId = toDeviceActorMsg.getDeviceId();
this.deviceMetaData = deviceMetaData;
}
@Override
public RuleId getRuleId() {
return ruleId;
}
@Override
public DeviceMetaData getDeviceMetaData() {
return deviceMetaData;
}
@Override
public Event save(Event event) {
checkEvent(event);
return eventService.save(event);
}
@Override
public Optional<Event> saveIfNotExists(Event event) {
checkEvent(event);
return eventService.saveIfNotExists(event);
}
@Override
public Optional<Event> findEvent(String eventType, String eventUid) {
return eventService.findEvent(tenantId, deviceId, eventType, eventUid);
}
@Override
public Alarm createOrUpdateAlarm(Alarm alarm) {
alarm.setTenantId(tenantId);
return alarmService.createOrUpdateAlarm(alarm);
}
public Optional<Alarm> findLatestAlarm(EntityId originator, String alarmType) {
try {
return Optional.ofNullable(alarmService.findLatestByOriginatorAndType(tenantId, originator, alarmType).get());
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException("Failed to lookup alarm!", e);
}
}
@Override
public ListenableFuture<Boolean> clearAlarm(AlarmId alarmId, long clearTs) {
return alarmService.clearAlarm(alarmId, clearTs);
}
private void checkEvent(Event event) {
if (event.getTenantId() == null) {
event.setTenantId(tenantId);
} else if (!tenantId.equals(event.getTenantId())) {
throw new IllegalArgumentException("Invalid Tenant id!");
}
if (event.getEntityId() == null) {
event.setEntityId(deviceId);
}
}
}

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

@ -0,0 +1,241 @@
/**
* 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.ruleChain;
import akka.actor.ActorRef;
import com.datastax.driver.core.utils.UUIDs;
import org.thingsboard.rule.engine.api.ListeningExecutor;
import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.rule.engine.api.RuleEngineDeviceRpcRequest;
import org.thingsboard.rule.engine.api.RuleEngineDeviceRpcResponse;
import org.thingsboard.rule.engine.api.RuleEngineRpcService;
import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
import org.thingsboard.rule.engine.api.ScriptEngine;
import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbRelationTypes;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId;
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.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.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.service.script.RuleNodeJsScriptEngine;
import scala.concurrent.duration.Duration;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
/**
* Created by ashvayka on 19.03.18.
*/
class DefaultTbContext implements TbContext {
private final ActorSystemContext mainCtx;
private final RuleNodeCtx nodeCtx;
public DefaultTbContext(ActorSystemContext mainCtx, RuleNodeCtx nodeCtx) {
this.mainCtx = mainCtx;
this.nodeCtx = nodeCtx;
}
@Override
public void tellNext(TbMsg msg, String relationType) {
tellNext(msg, Collections.singleton(relationType), null);
}
@Override
public void tellNext(TbMsg msg, Set<String> relationTypes) {
tellNext(msg, relationTypes, null);
}
@Override
public void tellNext(TbMsg msg, String relationType, Throwable th) {
tellNext(msg, Collections.singleton(relationType), th);
}
private void tellNext(TbMsg msg, Set<String> relationTypes, Throwable th) {
if (nodeCtx.getSelf().isDebugMode()) {
relationTypes.forEach(relationType -> mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg, relationType, th));
}
nodeCtx.getChainActor().tell(new RuleNodeToRuleChainTellNextMsg(nodeCtx.getSelf().getId(), relationTypes, msg), nodeCtx.getSelfActor());
}
@Override
public void tellSelf(TbMsg msg, long delayMs) {
//TODO: add persistence layer
scheduleMsgWithDelay(new RuleNodeToSelfMsg(msg), delayMs, nodeCtx.getSelfActor());
}
private void scheduleMsgWithDelay(Object msg, long delayInMs, ActorRef target) {
mainCtx.getScheduler().scheduleOnce(Duration.create(delayInMs, TimeUnit.MILLISECONDS), target, msg, mainCtx.getActorSystem().dispatcher(), nodeCtx.getSelfActor());
}
@Override
public void tellFailure(TbMsg msg, Throwable th) {
if (nodeCtx.getSelf().isDebugMode()) {
mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg, TbRelationTypes.FAILURE, th);
}
nodeCtx.getChainActor().tell(new RuleNodeToRuleChainTellNextMsg(nodeCtx.getSelf().getId(), Collections.singleton(TbRelationTypes.FAILURE), msg), nodeCtx.getSelfActor());
}
@Override
public void updateSelf(RuleNode self) {
nodeCtx.setSelf(self);
}
@Override
public TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, String data) {
return new TbMsg(UUIDs.timeBased(), type, originator, metaData.copy(), data, nodeCtx.getSelf().getRuleChainId(), nodeCtx.getSelf().getId(), mainCtx.getQueuePartitionId());
}
@Override
public TbMsg transformMsg(TbMsg origMsg, String type, EntityId originator, TbMsgMetaData metaData, String data) {
return new TbMsg(origMsg.getId(), type, originator, metaData.copy(), data, origMsg.getRuleChainId(), origMsg.getRuleNodeId(), mainCtx.getQueuePartitionId());
}
@Override
public RuleNodeId getSelfId() {
return nodeCtx.getSelf().getId();
}
@Override
public TenantId getTenantId() {
return nodeCtx.getTenantId();
}
@Override
public ListeningExecutor getJsExecutor() {
return mainCtx.getJsExecutor();
}
@Override
public ListeningExecutor getMailExecutor() {
return mainCtx.getMailExecutor();
}
@Override
public ListeningExecutor getDbCallbackExecutor() {
return mainCtx.getDbCallbackExecutor();
}
@Override
public ListeningExecutor getExternalCallExecutor() {
return mainCtx.getExternalCallExecutorService();
}
@Override
public ScriptEngine createJsScriptEngine(String script, String... argNames) {
return new RuleNodeJsScriptEngine(mainCtx.getJsSandbox(), script, argNames);
}
@Override
public AttributesService getAttributesService() {
return mainCtx.getAttributesService();
}
@Override
public CustomerService getCustomerService() {
return mainCtx.getCustomerService();
}
@Override
public UserService getUserService() {
return mainCtx.getUserService();
}
@Override
public AssetService getAssetService() {
return mainCtx.getAssetService();
}
@Override
public DeviceService getDeviceService() {
return mainCtx.getDeviceService();
}
@Override
public AlarmService getAlarmService() {
return mainCtx.getAlarmService();
}
@Override
public RuleChainService getRuleChainService() {
return mainCtx.getRuleChainService();
}
@Override
public TimeseriesService getTimeseriesService() {
return mainCtx.getTsService();
}
@Override
public RuleEngineTelemetryService getTelemetryService() {
return mainCtx.getTsSubService();
}
@Override
public RelationService getRelationService() {
return mainCtx.getRelationService();
}
@Override
public MailService getMailService() {
if (mainCtx.isAllowSystemMailService()) {
return mainCtx.getMailService();
} else {
throw new RuntimeException("Access to System Mail Service is forbidden!");
}
}
@Override
public RuleEngineRpcService getRpcService() {
return new RuleEngineRpcService() {
@Override
public void sendRpcReply(DeviceId deviceId, int requestId, String body) {
mainCtx.getDeviceRpcService().sendRpcReplyToDevice(nodeCtx.getTenantId(), deviceId, requestId, body);
}
@Override
public void sendRpcRequest(RuleEngineDeviceRpcRequest src, Consumer<RuleEngineDeviceRpcResponse> consumer) {
ToDeviceRpcRequest request = new ToDeviceRpcRequest(UUIDs.timeBased(), nodeCtx.getTenantId(), src.getDeviceId(),
src.isOneway(), System.currentTimeMillis() + src.getTimeout(), new ToDeviceRpcRequestBody(src.getMethod(), src.getBody()));
mainCtx.getDeviceRpcService().processRpcRequestToDevice(request, response -> {
consumer.accept(RuleEngineDeviceRpcResponse.builder()
.deviceId(src.getDeviceId())
.requestId(src.getRequestId())
.error(response.getError())
.response(response.getResponse())
.build());
});
}
};
}
}

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

@ -0,0 +1,97 @@
/**
* 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.ruleChain;
import akka.actor.OneForOneStrategy;
import akka.actor.SupervisorStrategy;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.device.DeviceActorToRuleEngineMsg;
import org.thingsboard.server.actors.service.ComponentActor;
import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
import scala.concurrent.duration.Duration;
public class RuleChainActor extends ComponentActor<RuleChainId, RuleChainActorMessageProcessor> {
private RuleChainActor(ActorSystemContext systemContext, TenantId tenantId, RuleChainId ruleChainId) {
super(systemContext, tenantId, ruleChainId);
setProcessor(new RuleChainActorMessageProcessor(tenantId, ruleChainId, systemContext,
logger, context().parent(), context().self()));
}
@Override
protected boolean process(TbActorMsg msg) {
switch (msg.getMsgType()) {
case COMPONENT_LIFE_CYCLE_MSG:
onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
break;
case SERVICE_TO_RULE_ENGINE_MSG:
processor.onServiceToRuleEngineMsg((ServiceToRuleEngineMsg) msg);
break;
case DEVICE_ACTOR_TO_RULE_ENGINE_MSG:
processor.onDeviceActorToRuleEngineMsg((DeviceActorToRuleEngineMsg) msg);
break;
case RULE_TO_RULE_CHAIN_TELL_NEXT_MSG:
processor.onTellNext((RuleNodeToRuleChainTellNextMsg) msg);
break;
case RULE_CHAIN_TO_RULE_CHAIN_MSG:
processor.onRuleChainToRuleChainMsg((RuleChainToRuleChainMsg) msg);
break;
case CLUSTER_EVENT_MSG:
break;
default:
return false;
}
return true;
}
public static class ActorCreator extends ContextBasedCreator<RuleChainActor> {
private static final long serialVersionUID = 1L;
private final TenantId tenantId;
private final RuleChainId ruleChainId;
public ActorCreator(ActorSystemContext context, TenantId tenantId, RuleChainId pluginId) {
super(context);
this.tenantId = tenantId;
this.ruleChainId = pluginId;
}
@Override
public RuleChainActor create() throws Exception {
return new RuleChainActor(context, tenantId, ruleChainId);
}
}
@Override
protected long getErrorPersistFrequency() {
return systemContext.getRuleChainErrorPersistFrequency();
}
@Override
public SupervisorStrategy supervisorStrategy() {
return strategy;
}
private final SupervisorStrategy strategy = new OneForOneStrategy(3, Duration.create("1 minute"), t -> {
logAndPersist("Unknown Failure", ActorSystemContext.toException(t));
return SupervisorStrategy.resume();
});
}

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

@ -0,0 +1,297 @@
/**
* 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.ruleChain;
import akka.actor.ActorContext;
import akka.actor.ActorRef;
import akka.actor.Props;
import akka.event.LoggingAdapter;
import com.datastax.driver.core.utils.UUIDs;
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;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
import org.thingsboard.server.dao.rule.RuleChainService;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @author Andrew Shvayka
*/
public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleChainId> {
private static final long DEFAULT_CLUSTER_PARTITION = 0L;
private final ActorRef parent;
private final ActorRef self;
private final Map<RuleNodeId, RuleNodeCtx> nodeActors;
private final Map<RuleNodeId, List<RuleNodeRelation>> nodeRoutes;
private final RuleChainService service;
private RuleNodeId firstId;
private RuleNodeCtx firstNode;
private boolean started;
RuleChainActorMessageProcessor(TenantId tenantId, RuleChainId ruleChainId, ActorSystemContext systemContext
, LoggingAdapter logger, ActorRef parent, ActorRef self) {
super(systemContext, logger, tenantId, ruleChainId);
this.parent = parent;
this.self = self;
this.nodeActors = new HashMap<>();
this.nodeRoutes = new HashMap<>();
this.service = systemContext.getRuleChainService();
}
@Override
public void start(ActorContext context) throws Exception {
if (!started) {
RuleChain ruleChain = service.findRuleChainById(entityId);
List<RuleNode> ruleNodeList = service.getRuleChainNodes(entityId);
// Creating and starting the actors;
for (RuleNode ruleNode : ruleNodeList) {
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 {
RuleChain ruleChain = service.findRuleChainById(entityId);
List<RuleNode> ruleNodeList = service.getRuleChainNodes(entityId);
for (RuleNode ruleNode : ruleNodeList) {
RuleNodeCtx existing = nodeActors.get(ruleNode.getId());
if (existing == null) {
ActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode);
nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode));
} else {
existing.setSelf(ruleNode);
existing.getSelfActor().tell(new ComponentLifecycleMsg(tenantId, existing.getSelf().getId(), ComponentLifecycleEvent.UPDATED), self);
}
}
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 -> {
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 {
nodeActors.values().stream().map(RuleNodeCtx::getSelfActor).forEach(context::stop);
nodeActors.clear();
nodeRoutes.clear();
context.stop(self);
started = false;
}
@Override
public void onClusterEventMsg(ClusterEventMsg msg) throws Exception {
}
private ActorRef createRuleNodeActor(ActorContext context, RuleNode ruleNode) {
String dispatcherName = tenantId.getId().equals(EntityId.NULL_UUID) ?
DefaultActorService.SYSTEM_RULE_DISPATCHER_NAME : DefaultActorService.TENANT_RULE_DISPATCHER_NAME;
return context.actorOf(
Props.create(new RuleNodeActor.ActorCreator(systemContext, tenantId, entityId, ruleNode.getId()))
.withDispatcher(dispatcherName), ruleNode.getId().toString());
}
private void initRoutes(RuleChain ruleChain, List<RuleNode> ruleNodeList) {
nodeRoutes.clear();
// Populating the routes map;
for (RuleNode ruleNode : ruleNodeList) {
List<EntityRelation> relations = service.getRuleNodeRelations(ruleNode.getId());
if (relations.size() == 0) {
nodeRoutes.put(ruleNode.getId(), Collections.emptyList());
} else {
for (EntityRelation relation : relations) {
if (relation.getTo().getEntityType() == EntityType.RULE_NODE) {
RuleNodeCtx ruleNodeCtx = nodeActors.get(new RuleNodeId(relation.getTo().getId()));
if (ruleNodeCtx == null) {
throw new IllegalArgumentException("Rule Node [" + relation.getFrom() + "] has invalid relation to Rule node [" + relation.getTo() + "]");
}
}
nodeRoutes.computeIfAbsent(ruleNode.getId(), k -> new ArrayList<>())
.add(new RuleNodeRelation(ruleNode.getId(), relation.getTo(), relation.getType()));
}
}
}
firstId = ruleChain.getFirstRuleNodeId();
firstNode = nodeActors.get(ruleChain.getFirstRuleNodeId());
state = ComponentLifecycleState.ACTIVE;
}
void onServiceToRuleEngineMsg(ServiceToRuleEngineMsg envelope) {
checkActive();
if (firstNode != null) {
putToQueue(enrichWithRuleChainId(envelope.getTbMsg()), msg -> pushMsgToNode(firstNode, msg, ""));
}
}
void onDeviceActorToRuleEngineMsg(DeviceActorToRuleEngineMsg envelope) {
checkActive();
if (firstNode != null) {
putToQueue(enrichWithRuleChainId(envelope.getTbMsg()), msg -> {
pushMsgToNode(firstNode, msg, "");
envelope.getCallbackRef().tell(new RuleEngineQueuePutAckMsg(msg.getId()), self);
});
}
}
void onRuleChainToRuleChainMsg(RuleChainToRuleChainMsg envelope) {
checkActive();
if (envelope.isEnqueue()) {
if (firstNode != null) {
putToQueue(enrichWithRuleChainId(envelope.getMsg()), msg -> pushMsgToNode(firstNode, msg, 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());
}
}
}
void onTellNext(RuleNodeToRuleChainTellNextMsg envelope) {
checkActive();
RuleNodeId originator = envelope.getOriginator();
List<RuleNodeRelation> relations = nodeRoutes.get(originator).stream()
.filter(r -> contains(envelope.getRelationTypes(), r.getType()))
.collect(Collectors.toList());
TbMsg msg = envelope.getMsg();
int relationsCount = relations.size();
EntityId ackId = msg.getRuleNodeId() != null ? msg.getRuleNodeId() : msg.getRuleChainId();
if (relationsCount == 0) {
queue.ack(tenantId, msg, ackId.getId(), msg.getClusterPartition());
} else if (relationsCount == 1) {
for (RuleNodeRelation relation : relations) {
pushToTarget(msg, relation.getOut(), relation.getType());
}
} else {
for (RuleNodeRelation relation : relations) {
EntityId target = relation.getOut();
switch (target.getEntityType()) {
case RULE_NODE:
enqueueAndForwardMsgCopyToNode(msg, target, relation.getType());
break;
case RULE_CHAIN:
enqueueAndForwardMsgCopyToChain(msg, target, relation.getType());
break;
}
}
//TODO: Ideally this should happen in async way when all targets confirm that the copied messages are successfully written to corresponding target queues.
queue.ack(tenantId, msg, ackId.getId(), msg.getClusterPartition());
}
}
private boolean contains(Set<String> relationTypes, String type) {
if (relationTypes == null) {
return true;
}
for (String relationType : relationTypes) {
if (relationType.equalsIgnoreCase(type)) {
return true;
}
}
return false;
}
private void enqueueAndForwardMsgCopyToChain(TbMsg msg, EntityId target, String fromRelationType) {
RuleChainId targetRCId = new RuleChainId(target.getId());
TbMsg copyMsg = msg.copy(UUIDs.timeBased(), targetRCId, null, DEFAULT_CLUSTER_PARTITION);
parent.tell(new RuleChainToRuleChainMsg(new RuleChainId(target.getId()), entityId, copyMsg, fromRelationType, true), self);
}
private void enqueueAndForwardMsgCopyToNode(TbMsg msg, EntityId target, String fromRelationType) {
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));
}
private void pushToTarget(TbMsg msg, EntityId target, String fromRelationType) {
switch (target.getEntityType()) {
case RULE_NODE:
pushMsgToNode(nodeActors.get(new RuleNodeId(target.getId())), msg, fromRelationType);
break;
case RULE_CHAIN:
parent.tell(new RuleChainToRuleChainMsg(new RuleChainId(target.getId()), entityId, msg, fromRelationType, false), self);
break;
}
}
private void pushMsgToNode(RuleNodeCtx nodeCtx, TbMsg msg, String fromRelationType) {
if (nodeCtx != null) {
nodeCtx.getSelfActor().tell(new RuleChainToRuleNodeMsg(new DefaultTbContext(systemContext, nodeCtx), msg, fromRelationType), self);
}
}
private TbMsg enrichWithRuleChainId(TbMsg tbMsg) {
// We don't put firstNodeId because it may change over time;
return new TbMsg(tbMsg.getId(), tbMsg.getType(), tbMsg.getOriginator(), tbMsg.getMetaData().copy(), tbMsg.getData(), entityId, null, systemContext.getQueuePartitionId());
}
}

57
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainManagerActor.java

@ -0,0 +1,57 @@
/**
* 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.ruleChain;
import akka.actor.ActorRef;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.service.ContextAwareActor;
import org.thingsboard.server.actors.shared.rulechain.RuleChainManager;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.dao.rule.RuleChainService;
/**
* Created by ashvayka on 15.03.18.
*/
public abstract class RuleChainManagerActor extends ContextAwareActor {
protected final RuleChainManager ruleChainManager;
protected final RuleChainService ruleChainService;
public RuleChainManagerActor(ActorSystemContext systemContext, RuleChainManager ruleChainManager) {
super(systemContext);
this.ruleChainManager = ruleChainManager;
this.ruleChainService = systemContext.getRuleChainService();
}
protected void initRuleChains() {
ruleChainManager.init(this.context());
}
protected ActorRef getEntityActorRef(EntityId entityId) {
ActorRef target = null;
switch (entityId.getEntityType()) {
case RULE_CHAIN:
target = ruleChainManager.getOrCreateActor(this.context(), (RuleChainId) entityId);
break;
}
return target;
}
protected void broadcast(Object msg) {
ruleChainManager.broadcast(msg);
}
}

40
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainToRuleChainMsg.java

@ -0,0 +1,40 @@
/**
* 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.ruleChain;
import lombok.Data;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.TbMsg;
/**
* Created by ashvayka on 19.03.18.
*/
@Data
public final class RuleChainToRuleChainMsg implements TbActorMsg {
private final RuleChainId target;
private final RuleChainId source;
private final TbMsg msg;
private final String fromRelationType;
private final boolean enqueue;
@Override
public MsgType getMsgType() {
return MsgType.RULE_CHAIN_TO_RULE_CHAIN_MSG;
}
}

38
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainToRuleNodeMsg.java

@ -0,0 +1,38 @@
/**
* 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.ruleChain;
import lombok.Data;
import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.TbMsg;
/**
* Created by ashvayka on 19.03.18.
*/
@Data
final class RuleChainToRuleNodeMsg implements TbActorMsg {
private final TbContext ctx;
private final TbMsg msg;
private final String fromRelationType;
@Override
public MsgType getMsgType() {
return MsgType.RULE_CHAIN_TO_RULE_MSG;
}
}

109
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActor.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.actors.ruleChain;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.service.ComponentActor;
import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
public class RuleNodeActor extends ComponentActor<RuleNodeId, RuleNodeActorMessageProcessor> {
private final RuleChainId ruleChainId;
private RuleNodeActor(ActorSystemContext systemContext, TenantId tenantId, RuleChainId ruleChainId, RuleNodeId ruleNodeId) {
super(systemContext, tenantId, ruleNodeId);
this.ruleChainId = ruleChainId;
setProcessor(new RuleNodeActorMessageProcessor(tenantId, ruleChainId, ruleNodeId, systemContext,
logger, context().parent(), context().self()));
}
@Override
protected boolean process(TbActorMsg msg) {
switch (msg.getMsgType()) {
case COMPONENT_LIFE_CYCLE_MSG:
onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
break;
case RULE_CHAIN_TO_RULE_MSG:
onRuleChainToRuleNodeMsg((RuleChainToRuleNodeMsg) msg);
break;
case RULE_TO_SELF_ERROR_MSG:
onRuleNodeToSelfErrorMsg((RuleNodeToSelfErrorMsg) msg);
break;
case RULE_TO_SELF_MSG:
onRuleNodeToSelfMsg((RuleNodeToSelfMsg) msg);
break;
default:
return false;
}
return true;
}
private void onRuleNodeToSelfMsg(RuleNodeToSelfMsg msg) {
logger.debug("[{}] Going to process rule msg: {}", id, msg.getMsg());
try {
processor.onRuleToSelfMsg(msg);
increaseMessagesProcessedCount();
} catch (Exception e) {
logAndPersist("onRuleMsg", e);
}
}
private void onRuleChainToRuleNodeMsg(RuleChainToRuleNodeMsg msg) {
logger.debug("[{}] Going to process rule msg: {}", id, msg.getMsg());
try {
processor.onRuleChainToRuleNodeMsg(msg);
increaseMessagesProcessedCount();
} catch (Exception e) {
logAndPersist("onRuleMsg", e);
}
}
private void onRuleNodeToSelfErrorMsg(RuleNodeToSelfErrorMsg msg) {
logAndPersist("onRuleMsg", ActorSystemContext.toException(msg.getError()));
}
public static class ActorCreator extends ContextBasedCreator<RuleNodeActor> {
private static final long serialVersionUID = 1L;
private final TenantId tenantId;
private final RuleChainId ruleChainId;
private final RuleNodeId ruleNodeId;
public ActorCreator(ActorSystemContext context, TenantId tenantId, RuleChainId ruleChainId, RuleNodeId ruleNodeId) {
super(context);
this.tenantId = tenantId;
this.ruleChainId = ruleChainId;
this.ruleNodeId = ruleNodeId;
}
@Override
public RuleNodeActor create() throws Exception {
return new RuleNodeActor(context, tenantId, ruleChainId, ruleNodeId);
}
}
@Override
protected long getErrorPersistFrequency() {
return systemContext.getRuleNodeErrorPersistFrequency();
}
}

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

@ -0,0 +1,121 @@
/**
* 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.ruleChain;
import akka.actor.ActorContext;
import akka.actor.ActorRef;
import akka.event.LoggingAdapter;
import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNode;
import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.shared.ComponentMsgProcessor;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.dao.rule.RuleChainService;
/**
* @author Andrew Shvayka
*/
public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNodeId> {
private final ActorRef parent;
private final ActorRef self;
private final RuleChainService service;
private RuleNode ruleNode;
private TbNode tbNode;
private TbContext defaultCtx;
RuleNodeActorMessageProcessor(TenantId tenantId, RuleChainId ruleChainId, RuleNodeId ruleNodeId, ActorSystemContext systemContext
, LoggingAdapter logger, ActorRef parent, ActorRef self) {
super(systemContext, logger, tenantId, ruleNodeId);
this.parent = parent;
this.self = self;
this.service = systemContext.getRuleChainService();
this.ruleNode = systemContext.getRuleChainService().findRuleNodeById(entityId);
this.defaultCtx = new DefaultTbContext(systemContext, new RuleNodeCtx(tenantId, parent, self, ruleNode));
}
@Override
public void start(ActorContext context) throws Exception {
tbNode = initComponent(ruleNode);
state = ComponentLifecycleState.ACTIVE;
}
@Override
public void onUpdate(ActorContext context) throws Exception {
RuleNode newRuleNode = systemContext.getRuleChainService().findRuleNodeById(entityId);
boolean restartRequired = !(ruleNode.getType().equals(newRuleNode.getType())
&& ruleNode.getConfiguration().equals(newRuleNode.getConfiguration()));
this.ruleNode = newRuleNode;
this.defaultCtx.updateSelf(newRuleNode);
if (restartRequired) {
if (tbNode != null) {
tbNode.destroy();
}
start(context);
}
}
@Override
public void stop(ActorContext context) throws Exception {
if (tbNode != null) {
tbNode.destroy();
}
context.stop(self);
}
@Override
public void onClusterEventMsg(ClusterEventMsg msg) throws Exception {
}
public void onRuleToSelfMsg(RuleNodeToSelfMsg msg) throws Exception {
checkActive();
if (ruleNode.isDebugMode()) {
systemContext.persistDebugInput(tenantId, entityId, msg.getMsg(), "Self");
}
try {
tbNode.onMsg(defaultCtx, msg.getMsg());
} catch (Exception e) {
defaultCtx.tellFailure(msg.getMsg(), e);
}
}
void onRuleChainToRuleNodeMsg(RuleChainToRuleNodeMsg msg) throws Exception {
checkActive();
if (ruleNode.isDebugMode()) {
systemContext.persistDebugInput(tenantId, entityId, msg.getMsg(), msg.getFromRelationType());
}
try {
tbNode.onMsg(msg.getCtx(), msg.getMsg());
} catch (Exception e) {
msg.getCtx().tellFailure(msg.getMsg(), e);
}
}
private TbNode initComponent(RuleNode ruleNode) throws Exception {
Class<?> componentClazz = Class.forName(ruleNode.getType());
TbNode tbNode = (TbNode) (componentClazz.newInstance());
tbNode.init(defaultCtx, new TbNodeConfiguration(ruleNode.getConfiguration()));
return tbNode;
}
}

19
extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/ToPluginRpcResponseDeviceMsg.java → application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeCtx.java

@ -13,19 +13,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.extensions.api.plugins.msg;
package org.thingsboard.server.actors.ruleChain;
import akka.actor.ActorRef;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.thingsboard.server.common.data.id.PluginId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.rule.RuleNode;
/**
* @author Andrew Shvayka
* Created by ashvayka on 19.03.18.
*/
@Data
public class ToPluginRpcResponseDeviceMsg implements ToPluginActorMsg {
private final PluginId pluginId;
private final TenantId pluginTenantId;
private final FromDeviceRpcResponse response;
@AllArgsConstructor
final class RuleNodeCtx {
private final TenantId tenantId;
private final ActorRef chainActor;
private final ActorRef selfActor;
private RuleNode self;
}

17
extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/MethodNameFilterConfiguration.java → application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeRelation.java

@ -13,21 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.extensions.core.filter;
package org.thingsboard.server.actors.ruleChain;
import lombok.Data;
import org.thingsboard.server.common.data.id.EntityId;
/**
* @author Andrew Shvayka
* Created by ashvayka on 19.03.18.
*/
@Data
public class MethodNameFilterConfiguration {
private MethodName[] methodNames;
@Data
final class RuleNodeRelation {
@Data
public static class MethodName {
private String name;
}
private final EntityId in;
private final EntityId out;
private final String type;
}

34
extensions/extension-rabbitmq/src/main/java/org/thingsboard/server/extensions/rabbitmq/plugin/RabbitMqPluginConfiguration.java → application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeToRuleChainTellNextMsg.java

@ -13,35 +13,29 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.extensions.rabbitmq.plugin;
package org.thingsboard.server.actors.ruleChain;
import lombok.Data;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.TbMsg;
import java.util.List;
import java.util.Set;
/**
* @author Andrew Shvayka
* Created by ashvayka on 19.03.18.
*/
@Data
public class RabbitMqPluginConfiguration {
private String host;
private int port;
private String virtualHost;
final class RuleNodeToRuleChainTellNextMsg implements TbActorMsg {
private String userName;
private String password;
private final RuleNodeId originator;
private final Set<String> relationTypes;
private final TbMsg msg;
private Boolean automaticRecoveryEnabled;
private Integer connectionTimeout;
private Integer handshakeTimeout;
private List<RabbitMqPluginProperties> clientProperties;
@Data
public static class RabbitMqPluginProperties {
private String key;
private String value;
@Override
public MsgType getMsgType() {
return MsgType.RULE_TO_RULE_CHAIN_TELL_NEXT_MSG;
}
}

37
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeToSelfErrorMsg.java

@ -0,0 +1,37 @@
/**
* 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.ruleChain;
import lombok.Data;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.TbMsg;
/**
* Created by ashvayka on 19.03.18.
*/
@Data
final class RuleNodeToSelfErrorMsg implements TbActorMsg {
private final TbMsg msg;
private final Throwable error;
@Override
public MsgType getMsgType() {
return MsgType.RULE_TO_SELF_ERROR_MSG;
}
}

36
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeToSelfMsg.java

@ -0,0 +1,36 @@
/**
* 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.ruleChain;
import lombok.Data;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.TbMsg;
/**
* Created by ashvayka on 19.03.18.
*/
@Data
final class RuleNodeToSelfMsg implements TbActorMsg {
private final TbMsg msg;
@Override
public MsgType getMsgType() {
return MsgType.RULE_TO_SELF_MSG;
}
}

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

@ -16,21 +16,24 @@
package org.thingsboard.server.actors.service;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.PluginId;
import org.thingsboard.server.common.data.id.RuleId;
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.cluster.SendToClusterMsg;
import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
import org.thingsboard.server.common.transport.SessionMsgProcessor;
import org.thingsboard.server.service.cluster.discovery.DiscoveryServiceListener;
import org.thingsboard.server.service.cluster.rpc.RpcMsgListener;
public interface ActorService extends SessionMsgProcessor, WebSocketMsgProcessor, RestMsgProcessor, RpcMsgListener, DiscoveryServiceListener {
public interface ActorService extends SessionMsgProcessor, RpcMsgListener, DiscoveryServiceListener {
void onPluginStateChange(TenantId tenantId, PluginId pluginId, ComponentLifecycleEvent state);
void onEntityStateChange(TenantId tenantId, EntityId entityId, ComponentLifecycleEvent state);
void onRuleStateChange(TenantId tenantId, RuleId ruleId, ComponentLifecycleEvent state);
void onMsg(SendToClusterMsg msg);
void onCredentialsUpdate(TenantId tenantId, DeviceId deviceId);
void onDeviceNameOrTypeUpdate(TenantId tenantId, DeviceId deviceId, String deviceName, String deviceType);
void onMsg(ServiceToRuleEngineMsg serviceToRuleEngineMsg);
}

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

@ -54,7 +54,7 @@ public abstract class ComponentActor<T extends EntityId, P extends ComponentMsgP
@Override
public void preStart() {
try {
processor.start();
processor.start(context());
logLifecycleEvent(ComponentLifecycleEvent.STARTED);
if (systemContext.isStatisticsEnabled()) {
scheduleStatsPersistTick();
@ -78,7 +78,7 @@ public abstract class ComponentActor<T extends EntityId, P extends ComponentMsgP
@Override
public void postStop() {
try {
processor.stop();
processor.stop(context());
logLifecycleEvent(ComponentLifecycleEvent.STOPPED);
} catch (Exception e) {
logger.warning("[{}][{}] Failed to stop {} processor: {}", tenantId, id, id.getEntityType(), e.getMessage());
@ -141,7 +141,6 @@ public abstract class ComponentActor<T extends EntityId, P extends ComponentMsgP
messagesProcessed++;
}
protected void logAndPersist(String method, Exception e) {
logAndPersist(method, e, false);
}

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

@ -16,9 +16,13 @@
package org.thingsboard.server.actors.service;
import akka.actor.UntypedActor;
import akka.event.Logging;
import akka.event.LoggingAdapter;
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);
public static final int ENTITY_PACK_LIMIT = 1024;
@ -28,4 +32,24 @@ public abstract class ContextAwareActor extends UntypedActor {
super();
this.systemContext = systemContext;
}
@Override
public void onReceive(Object msg) throws Exception {
if (logger.isDebugEnabled()) {
logger.debug("Processing msg: {}", msg);
}
if (msg instanceof TbActorMsg) {
try {
if (!process((TbActorMsg) msg)) {
logger.warning("Unknown message: {}!", msg);
}
} catch (Exception e) {
throw e;
}
} else {
logger.warning("Unknown message: {}!", msg);
}
}
protected abstract boolean process(TbActorMsg msg);
}

3
application/src/main/java/org/thingsboard/server/actors/service/ContextBasedCreator.java

@ -15,9 +15,8 @@
*/
package org.thingsboard.server.actors.service;
import org.thingsboard.server.actors.ActorSystemContext;
import akka.japi.Creator;
import org.thingsboard.server.actors.ActorSystemContext;
public abstract class ContextBasedCreator<T> implements Creator<T> {

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

@ -19,35 +19,32 @@ import akka.actor.ActorRef;
import akka.actor.ActorSystem;
import akka.actor.Props;
import akka.actor.Terminated;
import com.google.protobuf.ByteString;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.msg.DeviceCredentialsUpdateNotificationMsg;
import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg;
import org.thingsboard.server.actors.ActorSystemContext;
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.rpc.RpcSessionTellMsg;
import org.thingsboard.server.actors.session.SessionManagerActor;
import org.thingsboard.server.actors.stats.StatsActor;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.PluginId;
import org.thingsboard.server.common.data.id.RuleId;
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.core.ToDeviceSessionActorMsg;
import org.thingsboard.server.extensions.api.device.DeviceNameOrTypeUpdateMsg;
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.extensions.api.device.DeviceCredentialsUpdateNotificationMsg;
import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
import org.thingsboard.server.extensions.api.plugins.msg.ToPluginActorMsg;
import org.thingsboard.server.extensions.api.plugins.rest.PluginRestMsg;
import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg;
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;
import org.thingsboard.server.service.cluster.rpc.ClusterRpcService;
@ -57,7 +54,8 @@ import scala.concurrent.duration.Duration;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.Optional;
import static org.thingsboard.server.gen.cluster.ClusterAPIProtos.MessageType.CLUSTER_ACTOR_MESSAGE;
@Service
@Slf4j
@ -93,7 +91,7 @@ public class DefaultActorService implements ActorService {
@PostConstruct
public void initActorSystem() {
log.info("Initializing Actor system. {}", actorContext.getRuleService());
log.info("Initializing Actor system. {}", actorContext.getRuleChainService());
actorContext.setActorService(this);
system = ActorSystem.create(ACTOR_SYSTEM_NAME, actorContext.getConfig());
actorContext.setActorSystem(system);
@ -129,71 +127,16 @@ public class DefaultActorService implements ActorService {
}
@Override
public void process(SessionAwareMsg msg) {
log.debug("Processing session aware msg: {}", msg);
sessionManagerActor.tell(msg, ActorRef.noSender());
}
@Override
public void process(PluginWebsocketMsg<?> msg) {
log.debug("Processing websocket msg: {}", msg);
appActor.tell(msg, ActorRef.noSender());
}
@Override
public void process(PluginRestMsg msg) {
log.debug("Processing rest msg: {}", msg);
appActor.tell(msg, ActorRef.noSender());
}
@Override
public void onMsg(ToPluginActorMsg msg) {
log.trace("Processing plugin rpc msg: {}", msg);
appActor.tell(msg, ActorRef.noSender());
}
@Override
public void onMsg(ToDeviceActorMsg msg) {
log.trace("Processing device rpc msg: {}", msg);
public void onMsg(SendToClusterMsg msg) {
appActor.tell(msg, ActorRef.noSender());
}
@Override
public void onMsg(ToDeviceActorNotificationMsg msg) {
log.trace("Processing notification rpc msg: {}", msg);
appActor.tell(msg, ActorRef.noSender());
}
@Override
public void onMsg(ToDeviceSessionActorMsg msg) {
log.trace("Processing session rpc msg: {}", msg);
public void process(SessionAwareMsg msg) {
log.debug("Processing session aware msg: {}", msg);
sessionManagerActor.tell(msg, ActorRef.noSender());
}
@Override
public void onMsg(ToAllNodesMsg msg) {
log.trace("Processing broadcast rpc msg: {}", msg);
appActor.tell(msg, ActorRef.noSender());
}
@Override
public void onMsg(RpcSessionCreateRequestMsg msg) {
log.trace("Processing session create msg: {}", msg);
rpcManagerActor.tell(msg, ActorRef.noSender());
}
@Override
public void onMsg(RpcSessionTellMsg msg) {
log.trace("Processing session rpc msg: {}", msg);
rpcManagerActor.tell(msg, ActorRef.noSender());
}
@Override
public void onMsg(RpcBroadcastMsg msg) {
log.trace("Processing broadcast rpc msg: {}", msg);
rpcManagerActor.tell(msg, ActorRef.noSender());
}
@Override
public void onServerAdded(ServerInstance server) {
log.trace("Processing onServerAdded msg: {}", server);
@ -212,42 +155,37 @@ public class DefaultActorService implements ActorService {
}
@Override
public void onPluginStateChange(TenantId tenantId, PluginId pluginId, ComponentLifecycleEvent state) {
log.trace("[{}] Processing onPluginStateChange event: {}", pluginId, state);
broadcast(ComponentLifecycleMsg.forPlugin(tenantId, pluginId, state));
}
@Override
public void onRuleStateChange(TenantId tenantId, RuleId ruleId, ComponentLifecycleEvent state) {
log.trace("[{}] Processing onRuleStateChange event: {}", ruleId, state);
broadcast(ComponentLifecycleMsg.forRule(tenantId, ruleId, state));
public void onEntityStateChange(TenantId tenantId, EntityId entityId, ComponentLifecycleEvent state) {
log.trace("[{}] Processing {} state change event: {}", tenantId, entityId.getEntityType(), state);
broadcast(new ComponentLifecycleMsg(tenantId, entityId, state));
}
@Override
public void onCredentialsUpdate(TenantId tenantId, DeviceId deviceId) {
DeviceCredentialsUpdateNotificationMsg msg = new DeviceCredentialsUpdateNotificationMsg(tenantId, deviceId);
Optional<ServerAddress> address = actorContext.getRoutingService().resolveById(deviceId);
if (address.isPresent()) {
rpcService.tell(address.get(), msg);
} else {
onMsg(msg);
}
appActor.tell(new SendToClusterMsg(deviceId, msg), ActorRef.noSender());
}
@Override
public void onDeviceNameOrTypeUpdate(TenantId tenantId, DeviceId deviceId, String deviceName, String deviceType) {
log.trace("[{}] Processing onDeviceNameOrTypeUpdate event, deviceName: {}, deviceType: {}", deviceId, deviceName, deviceType);
DeviceNameOrTypeUpdateMsg msg = new DeviceNameOrTypeUpdateMsg(tenantId, deviceId, deviceName, deviceType);
Optional<ServerAddress> address = actorContext.getRoutingService().resolveById(deviceId);
if (address.isPresent()) {
rpcService.tell(address.get(), msg);
} else {
onMsg(msg);
}
appActor.tell(new SendToClusterMsg(deviceId, msg), ActorRef.noSender());
}
@Override
public void onMsg(ServiceToRuleEngineMsg msg) {
appActor.tell(msg, ActorRef.noSender());
}
public void broadcast(ToAllNodesMsg msg) {
rpcService.broadcast(msg);
actorContext.getEncodingService().encode(msg);
rpcService.broadcast(new RpcBroadcastMsg(ClusterAPIProtos.ClusterMessage
.newBuilder()
.setPayload(ByteString
.copyFrom(actorContext.getEncodingService().encode(msg)))
.setMessageType(CLUSTER_ACTOR_MESSAGE)
.build()));
appActor.tell(msg, ActorRef.noSender());
}
@ -256,4 +194,64 @@ public class DefaultActorService implements ActorService {
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);
if(log.isDebugEnabled()){
log.info("MSG: ", msg);
}
switch (msg.getMessageType()) {
case CLUSTER_ACTOR_MESSAGE:
java.util.Optional<TbActorMsg> decodedMsg = actorContext.getEncodingService()
.decode(msg.getPayload().toByteArray());
if (decodedMsg.isPresent()) {
appActor.tell(decodedMsg.get(), ActorRef.noSender());
} else {
log.error("Error during decoding cluster proto message");
}
break;
case TO_ALL_NODES_MSG:
//TODO
break;
case CLUSTER_TELEMETRY_SUBSCRIPTION_CREATE_MESSAGE:
actorContext.getTsSubService().onNewRemoteSubscription(serverAddress, msg.getPayload().toByteArray());
break;
case CLUSTER_TELEMETRY_SUBSCRIPTION_UPDATE_MESSAGE:
actorContext.getTsSubService().onRemoteSubscriptionUpdate(serverAddress, msg.getPayload().toByteArray());
break;
case CLUSTER_TELEMETRY_SUBSCRIPTION_CLOSE_MESSAGE:
actorContext.getTsSubService().onRemoteSubscriptionClose(serverAddress, msg.getPayload().toByteArray());
break;
case CLUSTER_TELEMETRY_SESSION_CLOSE_MESSAGE:
actorContext.getTsSubService().onRemoteSessionClose(serverAddress, msg.getPayload().toByteArray());
break;
case CLUSTER_TELEMETRY_ATTR_UPDATE_MESSAGE:
actorContext.getTsSubService().onRemoteAttributesUpdate(serverAddress, msg.getPayload().toByteArray());
break;
case CLUSTER_TELEMETRY_TS_UPDATE_MESSAGE:
actorContext.getTsSubService().onRemoteTsUpdate(serverAddress, msg.getPayload().toByteArray());
break;
case CLUSTER_RPC_FROM_DEVICE_RESPONSE_MESSAGE:
actorContext.getDeviceRpcService().processRemoteResponseFromDevice(serverAddress, msg.getPayload().toByteArray());
break;
}
}
@Override
public void onSendMsg(ClusterAPIProtos.ClusterMessage msg) {
rpcManagerActor.tell(msg, ActorRef.noSender());
}
@Override
public void onRpcSessionCreateRequestMsg(RpcSessionCreateRequestMsg msg) {
rpcManagerActor.tell(msg, ActorRef.noSender());
}
@Override
public void onBroadcastMsg(RpcBroadcastMsg msg) {
rpcManagerActor.tell(msg, ActorRef.noSender());
}
}

24
application/src/main/java/org/thingsboard/server/actors/service/WebSocketMsgProcessor.java

@ -1,24 +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.service;
import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg;
public interface WebSocketMsgProcessor {
void process(PluginWebsocketMsg<?> msg);
}

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

@ -15,19 +15,26 @@
*/
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.*;
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.device.ToDeviceActorMsg;
import org.thingsboard.server.common.msg.session.*;
import akka.actor.ActorContext;
import akka.event.LoggingAdapter;
import org.thingsboard.server.common.msg.session.ctrl.*;
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;
@ -37,7 +44,7 @@ import java.util.Optional;
class ASyncMsgProcessor extends AbstractSessionActorMsgProcessor {
private boolean firstMsg = true;
private Map<Integer, ToDeviceActorMsg> pendingMap = new HashMap<>();
private Map<Integer, DeviceToDeviceActorMsg> pendingMap = new HashMap<>();
private Optional<ServerAddress> currentTargetServer;
private boolean subscribedToAttributeUpdates;
private boolean subscribedToRpcCommands;
@ -47,14 +54,16 @@ class ASyncMsgProcessor extends AbstractSessionActorMsgProcessor {
}
@Override
protected void processToDeviceActorMsg(ActorContext ctx, ToDeviceActorSessionMsg msg) {
protected void processToDeviceActorMsg(ActorContext ctx, TransportToDeviceSessionActorMsg msg) {
updateSessionCtx(msg, SessionType.ASYNC);
DeviceToDeviceActorMsg pendingMsg = toDeviceMsg(msg);
FromDeviceMsg fromDeviceMsg = pendingMsg.getPayload();
if (firstMsg) {
toDeviceMsg(new SessionOpenMsg()).ifPresent(m -> forwardToAppActor(ctx, m));
if (fromDeviceMsg.getMsgType() != SessionMsgType.SESSION_OPEN) {
toDeviceMsg(new SessionOpenMsg()).ifPresent(m -> forwardToAppActor(ctx, m));
}
firstMsg = false;
}
ToDeviceActorMsg pendingMsg = toDeviceMsg(msg);
FromDeviceMsg fromDeviceMsg = pendingMsg.getPayload();
switch (fromDeviceMsg.getMsgType()) {
case POST_TELEMETRY_REQUEST:
case POST_ATTRIBUTES_REQUEST:
@ -86,8 +95,8 @@ class ASyncMsgProcessor extends AbstractSessionActorMsgProcessor {
@Override
public void processToDeviceMsg(ActorContext context, ToDeviceMsg msg) {
try {
if (msg.getMsgType() != MsgType.SESSION_CLOSE) {
switch (msg.getMsgType()) {
if (msg.getSessionMsgType() != SessionMsgType.SESSION_CLOSE) {
switch (msg.getSessionMsgType()) {
case STATUS_CODE_RESPONSE:
case GET_ATTRIBUTES_RESPONSE:
ResponseMsg responseMsg = (ResponseMsg) msg;

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

@ -15,36 +15,42 @@
*/
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.BasicToDeviceActorMsg;
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
import org.thingsboard.server.common.msg.session.*;
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 akka.actor.ActorContext;
import akka.actor.ActorRef;
import akka.event.LoggingAdapter;
import java.util.Optional;
abstract class AbstractSessionActorMsgProcessor extends AbstractContextAwareMsgProcessor {
protected final SessionId sessionId;
protected SessionContext sessionCtx;
protected ToDeviceActorMsg toDeviceActorMsgPrototype;
protected DeviceToDeviceActorMsg deviceToDeviceActorMsgPrototype;
protected AbstractSessionActorMsgProcessor(ActorSystemContext ctx, LoggingAdapter logger, SessionId sessionId) {
super(ctx, logger);
this.sessionId = sessionId;
}
protected abstract void processToDeviceActorMsg(ActorContext ctx, ToDeviceActorSessionMsg msg);
protected abstract void processToDeviceActorMsg(ActorContext ctx, TransportToDeviceSessionActorMsg msg);
protected abstract void processTimeoutMsg(ActorContext context, SessionTimeoutMsg msg);
@ -62,47 +68,44 @@ abstract class AbstractSessionActorMsgProcessor extends AbstractContextAwareMsgP
protected void cleanupSession(ActorContext ctx) {
}
protected void updateSessionCtx(ToDeviceActorSessionMsg msg, SessionType type) {
protected void updateSessionCtx(TransportToDeviceSessionActorMsg msg, SessionType type) {
sessionCtx = msg.getSessionMsg().getSessionContext();
toDeviceActorMsgPrototype = new BasicToDeviceActorMsg(msg, type);
deviceToDeviceActorMsgPrototype = new BasicDeviceToDeviceActorMsg(msg, type);
}
protected ToDeviceActorMsg toDeviceMsg(ToDeviceActorSessionMsg msg) {
protected DeviceToDeviceActorMsg toDeviceMsg(TransportToDeviceSessionActorMsg msg) {
AdaptorToSessionActorMsg adaptorMsg = msg.getSessionMsg();
return new BasicToDeviceActorMsg(toDeviceActorMsgPrototype, adaptorMsg.getMsg());
return new BasicDeviceToDeviceActorMsg(deviceToDeviceActorMsgPrototype, adaptorMsg.getMsg());
}
protected Optional<ToDeviceActorMsg> toDeviceMsg(FromDeviceMsg msg) {
if (toDeviceActorMsgPrototype != null) {
return Optional.of(new BasicToDeviceActorMsg(toDeviceActorMsgPrototype, msg));
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, ToDeviceActorMsg toForward) {
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> forwardToAppActorIfAdressChanged(ActorContext ctx, ToDeviceActorMsg toForward, Optional<ServerAddress> oldAddress) {
protected Optional<ServerAddress> forwardToAppActorIfAddressChanged(ActorContext ctx, DeviceToDeviceActorMsg toForward, Optional<ServerAddress> oldAddress) {
Optional<ServerAddress> newAddress = systemContext.getRoutingService().resolveById(toForward.getDeviceId());
if (!newAddress.equals(oldAddress)) {
if (newAddress.isPresent()) {
systemContext.getRpcService().tell(newAddress.get(),
toForward.toOtherAddress(systemContext.getRoutingService().getCurrentServer()));
} else {
getAppActor().tell(toForward, ctx.self());
}
getAppActor().tell(new SendToClusterMsg(toForward.getDeviceId(), toForward
.toOtherAddress(systemContext.getRoutingService().getCurrentServer())), ctx.self());
}
return newAddress;
}
protected void forwardToAppActor(ActorContext ctx, ToDeviceActorMsg toForward, Optional<ServerAddress> address) {
protected void forwardToAppActor(ActorContext ctx, DeviceToDeviceActorMsg toForward, Optional<ServerAddress> address) {
if (address.isPresent()) {
systemContext.getRpcService().tell(address.get(),
toForward.toOtherAddress(systemContext.getRoutingService().getCurrentServer()));
systemContext.getRpcService().tell(systemContext.getEncodingService().convertToProtoDataMessage(address.get(),
toForward.toOtherAddress(systemContext.getRoutingService().getCurrentServer())));
} else {
getAppActor().tell(toForward, ctx.self());
}
@ -114,6 +117,6 @@ abstract class AbstractSessionActorMsgProcessor extends AbstractContextAwareMsgP
}
public DeviceId getDeviceId() {
return toDeviceActorMsgPrototype.getDeviceId();
return deviceToDeviceActorMsgPrototype.getDeviceId();
}
}

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

@ -17,22 +17,21 @@ package org.thingsboard.server.actors.session;
import akka.actor.OneForOneStrategy;
import akka.actor.SupervisorStrategy;
import akka.japi.Function;
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.ToDeviceSessionActorMsg;
import org.thingsboard.server.common.msg.session.ToDeviceActorSessionMsg;
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 akka.event.Logging;
import akka.event.LoggingAdapter;
import scala.concurrent.duration.Duration;
public class SessionActor extends ContextAwareActor {
@ -61,33 +60,38 @@ public class SessionActor extends ContextAwareActor {
}
@Override
public void onReceive(Object msg) throws Exception {
logger.debug("[{}] Processing: {}.", sessionId, msg);
if (msg instanceof ToDeviceActorSessionMsg) {
processDeviceMsg((ToDeviceActorSessionMsg) msg);
} else if (msg instanceof ToDeviceSessionActorMsg) {
processToDeviceMsg((ToDeviceSessionActorMsg) msg);
} else if (msg instanceof SessionTimeoutMsg) {
processTimeoutMsg((SessionTimeoutMsg) msg);
} else if (msg instanceof SessionCtrlMsg) {
processSessionCtrlMsg((SessionCtrlMsg) msg);
} else if (msg instanceof ClusterEventMsg) {
processClusterEvent((ClusterEventMsg) msg);
} else {
logger.warning("[{}] Unknown msg: {}", sessionId, msg);
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 processDeviceMsg(ToDeviceActorSessionMsg msg) {
private void processTransportToSessionMsg(TransportToDeviceSessionActorMsg msg) {
initProcessor(msg);
processor.processToDeviceActorMsg(context(), msg);
}
private void processToDeviceMsg(ToDeviceSessionActorMsg msg) {
private void processActorsToSessionMsg(ActorSystemToDeviceSessionActorMsg msg) {
processor.processToDeviceMsg(context(), msg.getMsg());
}
@ -99,7 +103,7 @@ public class SessionActor extends ContextAwareActor {
}
}
private void processSessionCtrlMsg(SessionCtrlMsg msg) {
private void processSessionCloseMsg(SessionCtrlMsg msg) {
if (processor != null) {
processor.processSessionCtrlMsg(context(), msg);
} else if (msg instanceof SessionCloseMsg) {
@ -109,7 +113,7 @@ public class SessionActor extends ContextAwareActor {
}
}
private void initProcessor(ToDeviceActorSessionMsg msg) {
private void initProcessor(TransportToDeviceSessionActorMsg msg) {
if (processor == null) {
SessionMsg sessionMsg = (SessionMsg) msg.getSessionMsg();
if (sessionMsg.getSessionContext().getSessionType() == SessionType.SYNC) {

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

@ -15,26 +15,29 @@
*/
package org.thingsboard.server.actors.session;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import akka.actor.*;
import akka.actor.ActorRef;
import akka.actor.InvalidActorNameException;
import akka.actor.LocalActorRef;
import akka.actor.Props;
import akka.actor.Terminated;
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.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 akka.event.Logging;
import akka.event.LoggingAdapter;
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.core.ToDeviceSessionActorMsg;
import org.thingsboard.server.common.msg.session.SessionCtrlMsg;
import java.util.HashMap;
import java.util.Map;
public class SessionManagerActor extends ContextAwareActor {
private static final int INITIAL_SESSION_MAP_SIZE = 1024;
@ -48,6 +51,12 @@ public class SessionManagerActor extends ContextAwareActor {
this.sessionActors = new HashMap<>(INITIAL_SESSION_MAP_SIZE);
}
@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) {
@ -97,7 +106,7 @@ public class SessionManagerActor extends ContextAwareActor {
}
private void forwardToSessionActor(SessionAwareMsg msg) {
if (msg instanceof ToDeviceSessionActorMsg || msg instanceof SessionCloseMsg) {
if (msg instanceof ActorSystemToDeviceSessionActorMsg || msg instanceof SessionCloseMsg) {
String sessionIdStr = msg.getSessionId().toUidStr();
ActorRef sessionActor = sessionActors.get(sessionIdStr);
if (sessionActor != null) {

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

@ -15,24 +15,26 @@
*/
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.ToDeviceActorMsg;
import org.thingsboard.server.common.msg.session.*;
import org.thingsboard.server.common.msg.session.ToDeviceActorSessionMsg;
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 akka.actor.ActorContext;
import akka.event.LoggingAdapter;
import java.util.Optional;
class SyncMsgProcessor extends AbstractSessionActorMsgProcessor {
private ToDeviceActorMsg pendingMsg;
private DeviceToDeviceActorMsg pendingMsg;
private Optional<ServerAddress> currentTargetServer;
private boolean pendingResponse;
@ -41,7 +43,7 @@ class SyncMsgProcessor extends AbstractSessionActorMsgProcessor {
}
@Override
protected void processToDeviceActorMsg(ActorContext ctx, ToDeviceActorSessionMsg msg) {
protected void processToDeviceActorMsg(ActorContext ctx, TransportToDeviceSessionActorMsg msg) {
updateSessionCtx(msg, SessionType.SYNC);
pendingMsg = toDeviceMsg(msg);
pendingResponse = true;
@ -73,7 +75,7 @@ class SyncMsgProcessor extends AbstractSessionActorMsgProcessor {
@Override
public void processClusterEvent(ActorContext context, ClusterEventMsg msg) {
if (pendingResponse) {
Optional<ServerAddress> newTargetServer = forwardToAppActorIfAdressChanged(context, pendingMsg, currentTargetServer);
Optional<ServerAddress> newTargetServer = forwardToAppActorIfAddressChanged(context, pendingMsg, currentTargetServer);
if (logger.isDebugEnabled()) {
if (!newTargetServer.equals(currentTargetServer)) {
if (newTargetServer.isPresent()) {

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

@ -19,18 +19,13 @@ import akka.actor.ActorContext;
import akka.actor.ActorRef;
import akka.actor.Scheduler;
import akka.event.LoggingAdapter;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.extensions.api.component.*;
import scala.concurrent.ExecutionContextExecutor;
import scala.concurrent.duration.Duration;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
public abstract class AbstractContextAwareMsgProcessor {
@ -76,53 +71,6 @@ public abstract class AbstractContextAwareMsgProcessor {
getScheduler().scheduleOnce(Duration.create(delayInMs, TimeUnit.MILLISECONDS), target, msg, getSystemDispatcher(), null);
}
protected <T extends ConfigurableComponent> T initComponent(JsonNode componentNode) throws Exception {
ComponentConfiguration configuration = new ComponentConfiguration(
componentNode.get("clazz").asText(),
componentNode.get("name").asText(),
mapper.writeValueAsString(componentNode.get("configuration"))
);
logger.info("Initializing [{}][{}] component", configuration.getName(), configuration.getClazz());
ComponentDescriptor componentDescriptor = systemContext.getComponentService().getComponent(configuration.getClazz())
.orElseThrow(() -> new InstantiationException("Component Not found!"));
return initComponent(componentDescriptor, configuration);
}
protected <T extends ConfigurableComponent> T initComponent(ComponentDescriptor componentDefinition, ComponentConfiguration configuration)
throws Exception {
return initComponent(componentDefinition.getClazz(), componentDefinition.getType(), configuration.getConfiguration());
}
protected <T extends ConfigurableComponent> T initComponent(String clazz, ComponentType type, String configuration)
throws Exception {
Class<?> componentClazz = Class.forName(clazz);
T component = (T) (componentClazz.newInstance());
Class<?> configurationClazz;
switch (type) {
case FILTER:
configurationClazz = ((Filter) componentClazz.getAnnotation(Filter.class)).configuration();
break;
case PROCESSOR:
configurationClazz = ((Processor) componentClazz.getAnnotation(Processor.class)).configuration();
break;
case ACTION:
configurationClazz = ((Action) componentClazz.getAnnotation(Action.class)).configuration();
break;
case PLUGIN:
configurationClazz = ((Plugin) componentClazz.getAnnotation(Plugin.class)).configuration();
break;
default:
throw new IllegalStateException("Component with type: " + type + " is not supported!");
}
component.init(decode(configuration, configurationClazz));
return component;
}
public <C> C decode(String configuration, Class<C> configurationClazz) throws IOException, RuntimeException {
logger.info("Initializing using configuration: {}", configuration);
return mapper.readValue(configuration, configurationClazz);
}
@Data
@AllArgsConstructor
private static class ComponentConfiguration {

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

@ -17,39 +17,87 @@ package org.thingsboard.server.actors.shared;
import akka.actor.ActorContext;
import akka.event.LoggingAdapter;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.stats.StatsPersistTick;
import org.thingsboard.server.common.data.id.EntityId;
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;
public abstract class ComponentMsgProcessor<T> extends AbstractContextAwareMsgProcessor {
import javax.annotation.Nullable;
import java.util.function.Consumer;
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);
this.tenantId = tenantId;
this.entityId = id;
this.queue = systemContext.getMsgQueueService();
}
public abstract void start() throws Exception;
public abstract void start(ActorContext context) throws Exception;
public abstract void stop() throws Exception;
public abstract void stop(ActorContext context) throws Exception;
public abstract void onCreated(ActorContext context) throws Exception;
public abstract void onClusterEventMsg(ClusterEventMsg msg) throws Exception;
public abstract void onUpdate(ActorContext context) throws Exception;
public void onCreated(ActorContext context) throws Exception {
start(context);
}
public abstract void onActivate(ActorContext context) throws Exception;
public void onUpdate(ActorContext context) throws Exception {
restart(context);
}
public abstract void onSuspend(ActorContext context) throws Exception;
public void onActivate(ActorContext context) throws Exception {
restart(context);
}
public abstract void onStop(ActorContext context) throws Exception;
public void onSuspend(ActorContext context) throws Exception {
stop(context);
}
public abstract void onClusterEventMsg(ClusterEventMsg msg) throws Exception;
public void onStop(ActorContext context) throws Exception {
stop(context);
}
private void restart(ActorContext context) throws Exception {
stop(context);
start(context);
}
public void scheduleStatsPersistTick(ActorContext context, long statsPersistFrequency) {
schedulePeriodicMsgWithDelay(context, new StatsPersistTick(), statsPersistFrequency, statsPersistFrequency);
}
protected void checkActive() {
if (state != ComponentLifecycleState.ACTIVE) {
throw new IllegalStateException("Rule chain is not active!");
}
}
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);
}
});
}
}

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

@ -0,0 +1,84 @@
/**
* 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.shared;
import akka.actor.ActorContext;
import akka.actor.ActorRef;
import akka.actor.Props;
import akka.actor.UntypedActor;
import akka.japi.Creator;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.service.ContextAwareActor;
import org.thingsboard.server.common.data.SearchTextBased;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.page.PageDataIterable;
import java.util.HashMap;
import java.util.Map;
/**
* Created by ashvayka on 15.03.18.
*/
@Slf4j
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;
public EntityActorsManager(ActorSystemContext systemContext) {
this.systemContext = systemContext;
this.actors = new HashMap<>();
}
protected abstract TenantId getTenantId();
protected abstract String getDispatcherName();
protected abstract Creator<A> creator(T entityId);
protected abstract PageDataIterable.FetchFunction<M> getFetchEntitiesFunction();
public void init(ActorContext context) {
for (M entity : new PageDataIterable<>(getFetchEntitiesFunction(), ContextAwareActor.ENTITY_PACK_LIMIT)) {
T entityId = (T) entity.getId();
log.debug("[{}|{}] Creating entity actor", entityId.getEntityType(), entityId.getId());
//TODO: remove this cast making UUIDBased subclass of EntityId an interface and vice versa.
ActorRef actorRef = getOrCreateActor(context, entityId);
visit(entity, actorRef);
log.debug("[{}|{}] Entity actor created.", entityId.getEntityType(), entityId.getId());
}
}
public void visit(M entity, ActorRef actorRef) {}
public ActorRef getOrCreateActor(ActorContext context, T entityId) {
return actors.computeIfAbsent(entityId, eId ->
context.actorOf(Props.create(creator(eId))
.withDispatcher(getDispatcherName()), eId.toString()));
}
public void broadcast(Object msg) {
actors.values().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender()));
}
public void remove(T id) {
actors.remove(id);
}
}

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

@ -17,13 +17,20 @@ package org.thingsboard.server.actors.shared;
import lombok.Data;
import org.thingsboard.server.common.data.id.SessionId;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.TbActorMsg;
import java.io.Serializable;
@Data
public class SessionTimeoutMsg implements Serializable {
public class SessionTimeoutMsg implements Serializable, TbActorMsg {
private static final long serialVersionUID = 1L;
private final SessionId sessionId;
@Override
public MsgType getMsgType() {
return MsgType.SESSION_TIMEOUT_MSG;
}
}

77
application/src/main/java/org/thingsboard/server/actors/shared/plugin/PluginManager.java

@ -1,77 +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.shared.plugin;
import akka.actor.ActorContext;
import akka.actor.ActorRef;
import akka.actor.Props;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.plugin.PluginActor;
import org.thingsboard.server.actors.service.ContextAwareActor;
import org.thingsboard.server.common.data.id.PluginId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction;
import org.thingsboard.server.common.data.plugin.PluginMetaData;
import org.thingsboard.server.dao.plugin.PluginService;
import java.util.HashMap;
import java.util.Map;
@Slf4j
public abstract class PluginManager {
protected final ActorSystemContext systemContext;
protected final PluginService pluginService;
protected final Map<PluginId, ActorRef> pluginActors;
public PluginManager(ActorSystemContext systemContext) {
this.systemContext = systemContext;
this.pluginService = systemContext.getPluginService();
this.pluginActors = new HashMap<>();
}
public void init(ActorContext context) {
PageDataIterable<PluginMetaData> pluginIterator = new PageDataIterable<>(getFetchPluginsFunction(),
ContextAwareActor.ENTITY_PACK_LIMIT);
for (PluginMetaData plugin : pluginIterator) {
log.debug("[{}] Creating plugin actor", plugin.getId());
getOrCreatePluginActor(context, plugin.getId());
log.debug("Plugin actor created.");
}
}
abstract FetchFunction<PluginMetaData> getFetchPluginsFunction();
abstract TenantId getTenantId();
abstract String getDispatcherName();
public ActorRef getOrCreatePluginActor(ActorContext context, PluginId pluginId) {
return pluginActors.computeIfAbsent(pluginId, pId ->
context.actorOf(Props.create(new PluginActor.ActorCreator(systemContext, getTenantId(), pId))
.withDispatcher(getDispatcherName()), pId.toString()));
}
public void broadcast(Object msg) {
pluginActors.values().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender()));
}
public void remove(PluginId id) {
pluginActors.remove(id);
}
}

45
application/src/main/java/org/thingsboard/server/actors/shared/plugin/SystemPluginManager.java

@ -1,45 +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.shared.plugin;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction;
import org.thingsboard.server.common.data.plugin.PluginMetaData;
import org.thingsboard.server.dao.plugin.BasePluginService;
public class SystemPluginManager extends PluginManager {
public SystemPluginManager(ActorSystemContext systemContext) {
super(systemContext);
}
@Override
FetchFunction<PluginMetaData> getFetchPluginsFunction() {
return pluginService::findSystemPlugins;
}
@Override
TenantId getTenantId() {
return BasePluginService.SYSTEM_TENANT;
}
@Override
protected String getDispatcherName() {
return DefaultActorService.SYSTEM_PLUGIN_DISPATCHER_NAME;
}
}

135
application/src/main/java/org/thingsboard/server/actors/shared/rule/RuleManager.java

@ -1,135 +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.shared.rule;
import akka.actor.ActorContext;
import akka.actor.ActorRef;
import akka.actor.Props;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.rule.RuleActor;
import org.thingsboard.server.actors.rule.RuleActorChain;
import org.thingsboard.server.actors.rule.RuleActorMetaData;
import org.thingsboard.server.actors.rule.SimpleRuleActorChain;
import org.thingsboard.server.actors.service.ContextAwareActor;
import org.thingsboard.server.common.data.id.RuleId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
import org.thingsboard.server.common.data.rule.RuleMetaData;
import org.thingsboard.server.dao.rule.RuleService;
import java.util.*;
@Slf4j
public abstract class RuleManager {
protected final ActorSystemContext systemContext;
protected final RuleService ruleService;
protected final Map<RuleId, ActorRef> ruleActors;
protected final TenantId tenantId;
private Map<RuleMetaData, RuleActorMetaData> ruleMap;
private RuleActorChain ruleChain;
public RuleManager(ActorSystemContext systemContext, TenantId tenantId) {
this.systemContext = systemContext;
this.ruleService = systemContext.getRuleService();
this.ruleActors = new HashMap<>();
this.tenantId = tenantId;
}
public void init(ActorContext context) {
doInit(context);
}
private void doInit(ActorContext context) {
PageDataIterable<RuleMetaData> ruleIterator = new PageDataIterable<>(getFetchRulesFunction(),
ContextAwareActor.ENTITY_PACK_LIMIT);
ruleMap = new HashMap<>();
for (RuleMetaData rule : ruleIterator) {
log.debug("[{}] Creating rule actor {}", rule.getId(), rule);
ActorRef ref = getOrCreateRuleActor(context, rule.getId());
ruleMap.put(rule, RuleActorMetaData.systemRule(rule.getId(), rule.getWeight(), ref));
log.debug("[{}] Rule actor created.", rule.getId());
}
refreshRuleChain();
}
public Optional<ActorRef> update(ActorContext context, RuleId ruleId, ComponentLifecycleEvent event) {
if (ruleMap == null) {
doInit(context);
}
RuleMetaData rule;
if (event != ComponentLifecycleEvent.DELETED) {
rule = systemContext.getRuleService().findRuleById(ruleId);
} else {
rule = ruleMap.keySet().stream()
.filter(r -> r.getId().equals(ruleId))
.peek(r -> r.setState(ComponentLifecycleState.SUSPENDED))
.findFirst()
.orElse(null);
if (rule != null) {
ruleMap.remove(rule);
ruleActors.remove(ruleId);
}
}
if (rule != null) {
RuleActorMetaData actorMd = ruleMap.get(rule);
if (actorMd == null) {
ActorRef ref = getOrCreateRuleActor(context, rule.getId());
actorMd = RuleActorMetaData.systemRule(rule.getId(), rule.getWeight(), ref);
ruleMap.put(rule, actorMd);
}
refreshRuleChain();
return Optional.of(actorMd.getActorRef());
} else {
log.warn("[{}] Can't process unknown rule!", ruleId);
return Optional.empty();
}
}
abstract FetchFunction<RuleMetaData> getFetchRulesFunction();
abstract String getDispatcherName();
public ActorRef getOrCreateRuleActor(ActorContext context, RuleId ruleId) {
return ruleActors.computeIfAbsent(ruleId, rId ->
context.actorOf(Props.create(new RuleActor.ActorCreator(systemContext, tenantId, rId))
.withDispatcher(getDispatcherName()), rId.toString()));
}
public RuleActorChain getRuleChain(ActorContext context) {
if (ruleChain == null) {
doInit(context);
}
return ruleChain;
}
private void refreshRuleChain() {
Set<RuleActorMetaData> activeRuleSet = new HashSet<>();
for (Map.Entry<RuleMetaData, RuleActorMetaData> rule : ruleMap.entrySet()) {
if (rule.getKey().getState() == ComponentLifecycleState.ACTIVE) {
activeRuleSet.add(rule.getValue());
}
}
ruleChain = new SimpleRuleActorChain(activeRuleSet);
}
}

48
application/src/main/java/org/thingsboard/server/actors/shared/rule/TenantRuleManager.java

@ -1,48 +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.shared.rule;
import akka.actor.ActorContext;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction;
import org.thingsboard.server.common.data.rule.RuleMetaData;
public class TenantRuleManager extends RuleManager {
public TenantRuleManager(ActorSystemContext systemContext, TenantId tenantId) {
super(systemContext, tenantId);
}
@Override
public void init(ActorContext context) {
if (systemContext.isTenantComponentsInitEnabled()) {
super.init(context);
}
}
@Override
FetchFunction<RuleMetaData> getFetchRulesFunction() {
return link -> ruleService.findTenantRules(tenantId, link);
}
@Override
String getDispatcherName() {
return DefaultActorService.TENANT_RULE_DISPATCHER_NAME;
}
}

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

@ -0,0 +1,59 @@
/**
* 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.shared.rulechain;
import akka.actor.ActorRef;
import akka.japi.Creator;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.ruleChain.RuleChainActor;
import org.thingsboard.server.actors.shared.EntityActorsManager;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.dao.rule.RuleChainService;
/**
* Created by ashvayka on 15.03.18.
*/
@Slf4j
public abstract class RuleChainManager extends EntityActorsManager<RuleChainId, RuleChainActor, RuleChain> {
protected final RuleChainService service;
@Getter
protected RuleChain rootChain;
@Getter
protected ActorRef rootChainActor;
public RuleChainManager(ActorSystemContext systemContext) {
super(systemContext);
this.service = systemContext.getRuleChainService();
}
@Override
public Creator<RuleChainActor> creator(RuleChainId entityId) {
return new RuleChainActor.ActorCreator(systemContext, getTenantId(), entityId);
}
@Override
public void visit(RuleChain entity, ActorRef actorRef) {
if (entity.isRoot()) {
rootChain = entity;
rootChainActor = actorRef;
}
}
}

24
application/src/main/java/org/thingsboard/server/actors/shared/rule/SystemRuleManager.java → application/src/main/java/org/thingsboard/server/actors/shared/rulechain/SystemRuleChainManager.java

@ -13,28 +13,36 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors.shared.rule;
package org.thingsboard.server.actors.shared.rulechain;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction;
import org.thingsboard.server.common.data.rule.RuleMetaData;
import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.dao.model.ModelConstants;
public class SystemRuleManager extends RuleManager {
import java.util.Collections;
public SystemRuleManager(ActorSystemContext systemContext) {
super(systemContext, new TenantId(ModelConstants.NULL_UUID));
public class SystemRuleChainManager extends RuleChainManager {
public SystemRuleChainManager(ActorSystemContext systemContext) {
super(systemContext);
}
@Override
protected FetchFunction<RuleChain> getFetchEntitiesFunction() {
return link -> new TextPageData<>(Collections.emptyList(), link);
}
@Override
FetchFunction<RuleMetaData> getFetchRulesFunction() {
return ruleService::findSystemRules;
protected TenantId getTenantId() {
return ModelConstants.SYSTEM_TENANT;
}
@Override
String getDispatcherName() {
protected String getDispatcherName() {
return DefaultActorService.SYSTEM_RULE_DISPATCHER_NAME;
}
}

21
application/src/main/java/org/thingsboard/server/actors/shared/plugin/TenantPluginManager.java → application/src/main/java/org/thingsboard/server/actors/shared/rulechain/TenantRuleChainManager.java

@ -13,20 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors.shared.plugin;
package org.thingsboard.server.actors.shared.rulechain;
import akka.actor.ActorContext;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction;
import org.thingsboard.server.common.data.plugin.PluginMetaData;
import org.thingsboard.server.common.data.rule.RuleChain;
public class TenantPluginManager extends PluginManager {
public class TenantRuleChainManager extends RuleChainManager {
private final TenantId tenantId;
public TenantPluginManager(ActorSystemContext systemContext, TenantId tenantId) {
public TenantRuleChainManager(ActorSystemContext systemContext, TenantId tenantId) {
super(systemContext);
this.tenantId = tenantId;
}
@ -39,18 +39,17 @@ public class TenantPluginManager extends PluginManager {
}
@Override
FetchFunction<PluginMetaData> getFetchPluginsFunction() {
return link -> pluginService.findTenantPlugins(tenantId, link);
}
@Override
TenantId getTenantId() {
protected TenantId getTenantId() {
return tenantId;
}
@Override
protected String getDispatcherName() {
return DefaultActorService.TENANT_PLUGIN_DISPATCHER_NAME;
return DefaultActorService.TENANT_RULE_DISPATCHER_NAME;
}
@Override
protected FetchFunction<RuleChain> getFetchEntitiesFunction() {
return link -> service.findTenantRuleChains(tenantId, link);
}
}

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

@ -24,6 +24,7 @@ import org.thingsboard.server.actors.service.ContextAwareActor;
import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Event;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
public class StatsActor extends ContextAwareActor {
@ -35,6 +36,12 @@ public class StatsActor extends ContextAwareActor {
super(context);
}
@Override
protected boolean process(TbActorMsg msg) {
//TODO Move everything here, to work with TbActorMsg\
return false;
}
@Override
public void onReceive(Object msg) throws Exception {
logger.debug("Received message: {}", msg);

40
application/src/main/java/org/thingsboard/server/actors/tenant/RuleChainDeviceMsg.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.tenant;
import org.thingsboard.server.actors.rule.RuleActorChain;
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
public class RuleChainDeviceMsg {
private final ToDeviceActorMsg toDeviceActorMsg;
private final RuleActorChain ruleChain;
public RuleChainDeviceMsg(ToDeviceActorMsg toDeviceActorMsg, RuleActorChain ruleChain) {
super();
this.toDeviceActorMsg = toDeviceActorMsg;
this.ruleChain = ruleChain;
}
public ToDeviceActorMsg getToDeviceActorMsg() {
return toDeviceActorMsg;
}
public RuleActorChain getRuleChain() {
return ruleChain;
}
}

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

@ -15,61 +15,55 @@
*/
package org.thingsboard.server.actors.tenant;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import akka.actor.ActorRef;
import akka.actor.OneForOneStrategy;
import akka.actor.Props;
import akka.actor.SupervisorStrategy;
import akka.japi.Function;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.device.DeviceActor;
import org.thingsboard.server.actors.plugin.PluginTerminationMsg;
import org.thingsboard.server.actors.rule.ComplexRuleActorChain;
import org.thingsboard.server.actors.rule.RuleActorChain;
import org.thingsboard.server.actors.service.ContextAwareActor;
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.plugin.PluginManager;
import org.thingsboard.server.actors.shared.plugin.TenantPluginManager;
import org.thingsboard.server.actors.shared.rule.RuleManager;
import org.thingsboard.server.actors.shared.rule.TenantRuleManager;
import org.thingsboard.server.actors.shared.rulechain.TenantRuleChainManager;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.PluginId;
import org.thingsboard.server.common.data.id.RuleId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
import akka.actor.ActorRef;
import akka.actor.Props;
import akka.event.Logging;
import akka.event.LoggingAdapter;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.aware.DeviceAwareMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
import org.thingsboard.server.extensions.api.plugins.msg.ToPluginActorMsg;
import org.thingsboard.server.extensions.api.rules.ToRuleActorMsg;
import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
import scala.concurrent.duration.Duration;
public class TenantActor extends ContextAwareActor {
import java.util.HashMap;
import java.util.Map;
private final LoggingAdapter logger = Logging.getLogger(getContext().system(), this);
public class TenantActor extends RuleChainManagerActor {
private final TenantId tenantId;
private final RuleManager ruleManager;
private final PluginManager pluginManager;
private final Map<DeviceId, ActorRef> deviceActors;
private TenantActor(ActorSystemContext systemContext, TenantId tenantId) {
super(systemContext);
super(systemContext, new TenantRuleChainManager(systemContext, tenantId));
this.tenantId = tenantId;
this.ruleManager = new TenantRuleManager(systemContext, tenantId);
this.pluginManager = new TenantPluginManager(systemContext, tenantId);
this.deviceActors = new HashMap<>();
}
@Override
public SupervisorStrategy supervisorStrategy() {
return strategy;
}
@Override
public void preStart() {
logger.info("[{}] Starting tenant actor.", tenantId);
try {
ruleManager.init(this.context());
pluginManager.init(this.context());
initRuleChains();
logger.info("[{}] Tenant actor started.", tenantId);
} catch (Exception e) {
logger.error(e, "[{}] Unknown failure", tenantId);
@ -77,89 +71,74 @@ public class TenantActor extends ContextAwareActor {
}
@Override
public void onReceive(Object msg) throws Exception {
logger.debug("[{}] Received message: {}", tenantId, msg);
if (msg instanceof RuleChainDeviceMsg) {
process((RuleChainDeviceMsg) msg);
} else if (msg instanceof ToDeviceActorMsg) {
onToDeviceActorMsg((ToDeviceActorMsg) msg);
} else if (msg instanceof ToPluginActorMsg) {
onToPluginMsg((ToPluginActorMsg) msg);
} else if (msg instanceof ToRuleActorMsg) {
onToRuleMsg((ToRuleActorMsg) msg);
} else if (msg instanceof ToDeviceActorNotificationMsg) {
onToDeviceActorMsg((ToDeviceActorNotificationMsg) msg);
} else if (msg instanceof ClusterEventMsg) {
broadcast(msg);
} else if (msg instanceof ComponentLifecycleMsg) {
onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
} else if (msg instanceof PluginTerminationMsg) {
onPluginTerminated((PluginTerminationMsg) msg);
} else {
logger.warning("[{}] Unknown message: {}!", tenantId, msg);
protected boolean process(TbActorMsg msg) {
switch (msg.getMsgType()) {
case CLUSTER_EVENT_MSG:
broadcast(msg);
break;
case COMPONENT_LIFE_CYCLE_MSG:
onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
break;
case SERVICE_TO_RULE_ENGINE_MSG:
onServiceToRuleEngineMsg((ServiceToRuleEngineMsg) msg);
break;
case DEVICE_ACTOR_TO_RULE_ENGINE_MSG:
onDeviceActorToRuleEngineMsg((DeviceActorToRuleEngineMsg) msg);
break;
case DEVICE_SESSION_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:
case DEVICE_RPC_REQUEST_TO_DEVICE_ACTOR_MSG:
case SERVER_RPC_RESPONSE_TO_DEVICE_ACTOR_MSG:
onToDeviceActorMsg((DeviceAwareMsg) msg);
break;
case RULE_CHAIN_TO_RULE_CHAIN_MSG:
onRuleChainMsg((RuleChainToRuleChainMsg) msg);
break;
default:
return false;
}
return true;
}
private void broadcast(Object msg) {
pluginManager.broadcast(msg);
@Override
protected void broadcast(Object msg) {
super.broadcast(msg);
deviceActors.values().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender()));
}
private void onToDeviceActorMsg(ToDeviceActorMsg msg) {
getOrCreateDeviceActor(msg.getDeviceId()).tell(msg, ActorRef.noSender());
private void onServiceToRuleEngineMsg(ServiceToRuleEngineMsg msg) {
ruleChainManager.getRootChainActor().tell(msg, self());
}
private void onToDeviceActorMsg(ToDeviceActorNotificationMsg msg) {
getOrCreateDeviceActor(msg.getDeviceId()).tell(msg, ActorRef.noSender());
private void onDeviceActorToRuleEngineMsg(DeviceActorToRuleEngineMsg msg) {
ruleChainManager.getRootChainActor().tell(msg, self());
}
private void onToRuleMsg(ToRuleActorMsg msg) {
ActorRef target = ruleManager.getOrCreateRuleActor(this.context(), msg.getRuleId());
target.tell(msg, ActorRef.noSender());
private void onRuleChainMsg(RuleChainToRuleChainMsg msg) {
ruleChainManager.getOrCreateActor(context(), msg.getTarget()).tell(msg, self());
}
private void onToPluginMsg(ToPluginActorMsg msg) {
if (msg.getPluginTenantId().equals(tenantId)) {
ActorRef pluginActor = pluginManager.getOrCreatePluginActor(this.context(), msg.getPluginId());
pluginActor.tell(msg, ActorRef.noSender());
} else {
context().parent().tell(msg, ActorRef.noSender());
}
private void onToDeviceActorMsg(DeviceAwareMsg msg) {
getOrCreateDeviceActor(msg.getDeviceId()).tell(msg, ActorRef.noSender());
}
private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) {
Optional<PluginId> pluginId = msg.getPluginId();
Optional<RuleId> ruleId = msg.getRuleId();
if (pluginId.isPresent()) {
ActorRef pluginActor = pluginManager.getOrCreatePluginActor(this.context(), pluginId.get());
pluginActor.tell(msg, ActorRef.noSender());
} else if (ruleId.isPresent()) {
ActorRef target;
Optional<ActorRef> ref = ruleManager.update(this.context(), ruleId.get(), msg.getEvent());
if (ref.isPresent()) {
target = ref.get();
} else {
logger.debug("Failed to find actor for rule: [{}]", ruleId);
return;
ActorRef target = getEntityActorRef(msg.getEntityId());
if (target != null) {
if (msg.getEntityId().getEntityType() == EntityType.RULE_CHAIN) {
RuleChain ruleChain = systemContext.getRuleChainService().
findRuleChainById(new RuleChainId(msg.getEntityId().getId()));
ruleChainManager.visit(ruleChain, target);
}
target.tell(msg, ActorRef.noSender());
} else {
logger.debug("[{}] Invalid component lifecycle msg.", tenantId);
logger.debug("Invalid component lifecycle msg: {}", msg);
}
}
private void onPluginTerminated(PluginTerminationMsg msg) {
pluginManager.remove(msg.getId());
}
private void process(RuleChainDeviceMsg msg) {
ToDeviceActorMsg toDeviceActorMsg = msg.getToDeviceActorMsg();
ActorRef deviceActor = getOrCreateDeviceActor(toDeviceActorMsg.getDeviceId());
RuleActorChain tenantChain = ruleManager.getRuleChain(this.context());
RuleActorChain chain = new ComplexRuleActorChain(msg.getRuleChain(), tenantChain);
deviceActor.tell(new RuleChainDeviceMsg(toDeviceActorMsg, chain), context().self());
}
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()));
@ -181,4 +160,12 @@ public class TenantActor extends ContextAwareActor {
}
}
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.resume();
}
});
}

2
application/src/main/java/org/thingsboard/server/config/AuditLogLevelProperties.java

@ -17,8 +17,6 @@ package org.thingsboard.server.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.audit.ActionType;
import java.util.HashMap;
import java.util.Map;

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

@ -24,7 +24,11 @@ import org.springframework.context.annotation.Configuration;
import org.thingsboard.server.common.data.security.Authority;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.schema.AlternateTypeRule;
import springfox.documentation.service.*;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.Contact;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;

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

@ -20,7 +20,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
@ -37,15 +36,18 @@ import org.springframework.security.web.authentication.AuthenticationFailureHand
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.cors.CorsUtils;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.thingsboard.server.dao.audit.AuditLogLevelFilter;
import org.thingsboard.server.exception.ThingsboardErrorResponseHandler;
import org.thingsboard.server.service.security.auth.jwt.JwtAuthenticationProvider;
import org.thingsboard.server.service.security.auth.jwt.JwtTokenAuthenticationProcessingFilter;
import org.thingsboard.server.service.security.auth.jwt.RefreshTokenAuthenticationProvider;
import org.thingsboard.server.service.security.auth.jwt.RefreshTokenProcessingFilter;
import org.thingsboard.server.service.security.auth.jwt.SkipPathRequestMatcher;
import org.thingsboard.server.service.security.auth.jwt.extractor.TokenExtractor;
import org.thingsboard.server.service.security.auth.rest.RestAuthenticationProvider;
import org.thingsboard.server.service.security.auth.rest.RestLoginProcessingFilter;
import org.thingsboard.server.service.security.auth.jwt.*;
import org.thingsboard.server.service.security.auth.jwt.extractor.TokenExtractor;
import org.thingsboard.server.service.security.auth.rest.RestPublicLoginProcessingFilter;
import java.util.ArrayList;

18
application/src/main/java/org/thingsboard/server/config/WebSocketConfiguration.java

@ -15,12 +15,6 @@
*/
package org.thingsboard.server.config;
import java.util.Map;
import org.thingsboard.server.exception.ThingsboardErrorCode;
import org.thingsboard.server.exception.ThingsboardException;
import org.thingsboard.server.controller.plugin.PluginWebSocketHandler;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
@ -35,6 +29,12 @@ import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry
import org.springframework.web.socket.server.HandshakeInterceptor;
import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.controller.plugin.TbWebSocketHandler;
import org.thingsboard.server.service.security.model.SecurityUser;
import java.util.Map;
@Configuration
@EnableWebSocket
@ -54,7 +54,7 @@ public class WebSocketConfiguration implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(pluginWsHandler(), WS_PLUGIN_MAPPING).setAllowedOrigins("*")
registry.addHandler(wsHandler(), WS_PLUGIN_MAPPING).setAllowedOrigins("*")
.addInterceptors(new HttpSessionHandshakeInterceptor(), new HandshakeInterceptor() {
@Override
@ -82,8 +82,8 @@ public class WebSocketConfiguration implements WebSocketConfigurer {
}
@Bean
public WebSocketHandler pluginWsHandler() {
return new PluginWebSocketHandler();
public WebSocketHandler wsHandler() {
return new TbWebSocketHandler();
}
protected SecurityUser getCurrentUser() throws ThingsboardException {

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

@ -17,11 +17,16 @@ package org.thingsboard.server.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.dao.settings.AdminSettingsService;
import org.thingsboard.server.exception.ThingsboardException;
import org.thingsboard.server.service.mail.MailService;
import org.thingsboard.server.service.update.UpdateService;
import org.thingsboard.server.service.update.model.UpdateMessage;

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

@ -18,13 +18,27 @@ package org.thingsboard.server.controller;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.thingsboard.server.common.data.alarm.*;
import org.thingsboard.server.common.data.id.*;
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.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmId;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.exception.ThingsboardErrorCode;
import org.thingsboard.server.exception.ThingsboardException;
@RestController
@RequestMapping("/api")
@ -93,7 +107,7 @@ public class AlarmController extends BaseController {
try {
AlarmId alarmId = new AlarmId(toUUID(strAlarmId));
checkAlarmId(alarmId);
alarmService.clearAlarm(alarmId, System.currentTimeMillis()).get();
alarmService.clearAlarm(alarmId, null, System.currentTimeMillis()).get();
} catch (Exception e) {
throw handleException(e);
}

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

@ -18,23 +18,30 @@ package org.thingsboard.server.controller;
import com.google.common.util.concurrent.ListenableFuture;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
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.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetSearchQuery;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId;
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.asset.AssetSearchQuery;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.exception.ThingsboardErrorCode;
import org.thingsboard.server.exception.ThingsboardException;
import org.thingsboard.server.service.security.model.SecurityUser;
import java.util.ArrayList;

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

@ -16,15 +16,20 @@
package org.thingsboard.server.controller;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.audit.AuditLog;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.exception.ThingsboardException;
import java.util.UUID;

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

@ -25,12 +25,18 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.bind.annotation.*;
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.rule.engine.api.MailService;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.exception.ThingsboardErrorCode;
import org.thingsboard.server.exception.ThingsboardException;
import org.thingsboard.server.service.mail.MailService;
import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.UserPrincipal;

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

@ -15,12 +15,13 @@
*/
package org.thingsboard.server.controller;
import com.fasterxml.jackson.databind.JsonNode;
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.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;
@ -30,18 +31,24 @@ import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmId;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.audit.ActionStatus;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.*;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.DataType;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.data.plugin.PluginMetaData;
import org.thingsboard.server.common.data.rule.RuleMetaData;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.widget.WidgetType;
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.system.ServiceToRuleEngineMsg;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.audit.AuditLogService;
@ -52,23 +59,23 @@ import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.plugin.PluginService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.dao.widget.WidgetTypeService;
import org.thingsboard.server.dao.widget.WidgetsBundleService;
import org.thingsboard.server.exception.ThingsboardErrorCode;
import org.thingsboard.server.exception.ThingsboardErrorResponseHandler;
import org.thingsboard.server.exception.ThingsboardException;
import org.thingsboard.server.service.component.ComponentDiscoveryService;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.state.DeviceStateService;
import javax.mail.MessagingException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import static org.thingsboard.server.dao.service.Validator.validateId;
@ -79,9 +86,14 @@ public abstract class BaseController {
public static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
public static final String YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION = "You don't have permission to perform this operation!";
private static final ObjectMapper json = new ObjectMapper();
@Autowired
private ThingsboardErrorResponseHandler errorResponseHandler;
@Autowired
protected TenantService tenantService;
@Autowired
protected CustomerService customerService;
@ -113,10 +125,7 @@ public abstract class BaseController {
protected ComponentDiscoveryService componentDescriptorService;
@Autowired
protected RuleService ruleService;
@Autowired
protected PluginService pluginService;
protected RuleChainService ruleChainService;
@Autowired
protected ActorService actorService;
@ -127,6 +136,9 @@ public abstract class BaseController {
@Autowired
protected AuditLogService auditLogService;
@Autowired
protected DeviceStateService deviceStateService;
@ExceptionHandler(ThingsboardException.class)
public void handleThingsboardException(ThingsboardException ex, HttpServletResponse response) {
errorResponseHandler.handle(ex, response);
@ -289,11 +301,8 @@ public abstract class BaseController {
case TENANT:
checkTenantId(new TenantId(entityId.getId()));
return;
case PLUGIN:
checkPlugin(new PluginId(entityId.getId()));
return;
case RULE:
checkRule(new RuleId(entityId.getId()));
case RULE_CHAIN:
checkRuleChain(new RuleChainId(entityId.getId()));
return;
case ASSET:
checkAsset(assetService.findAssetById(new AssetId(entityId.getId())));
@ -472,60 +481,34 @@ public abstract class BaseController {
}
}
List<ComponentDescriptor> checkPluginActionsByPluginClazz(String pluginClazz) throws ThingsboardException {
List<ComponentDescriptor> checkComponentDescriptorsByTypes(Set<ComponentType> types) throws ThingsboardException {
try {
checkComponentDescriptorByClazz(pluginClazz);
log.debug("[{}] Lookup plugin actions", pluginClazz);
return componentDescriptorService.getPluginActions(pluginClazz);
log.debug("[{}] Lookup component descriptors", types);
return componentDescriptorService.getComponents(types);
} catch (Exception e) {
throw handleException(e, false);
}
}
protected PluginMetaData checkPlugin(PluginMetaData plugin) throws ThingsboardException {
checkNotNull(plugin);
SecurityUser authUser = getCurrentUser();
TenantId tenantId = plugin.getTenantId();
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
if (authUser.getAuthority() != Authority.SYS_ADMIN) {
if (authUser.getTenantId() == null ||
!tenantId.getId().equals(ModelConstants.NULL_UUID) && !authUser.getTenantId().equals(tenantId)) {
throw new ThingsboardException(YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION,
ThingsboardErrorCode.PERMISSION_DENIED);
} else if (tenantId.getId().equals(ModelConstants.NULL_UUID)) {
plugin.setConfiguration(null);
}
}
return plugin;
}
protected PluginMetaData checkPlugin(PluginId pluginId) throws ThingsboardException {
checkNotNull(pluginId);
return checkPlugin(pluginService.findPluginById(pluginId));
}
protected RuleMetaData checkRule(RuleId ruleId) throws ThingsboardException {
checkNotNull(ruleId);
return checkRule(ruleService.findRuleById(ruleId));
protected RuleChain checkRuleChain(RuleChainId ruleChainId) throws ThingsboardException {
checkNotNull(ruleChainId);
return checkRuleChain(ruleChainService.findRuleChainById(ruleChainId));
}
protected RuleMetaData checkRule(RuleMetaData rule) throws ThingsboardException {
checkNotNull(rule);
protected RuleChain checkRuleChain(RuleChain ruleChain) throws ThingsboardException {
checkNotNull(ruleChain);
SecurityUser authUser = getCurrentUser();
TenantId tenantId = rule.getTenantId();
TenantId tenantId = ruleChain.getTenantId();
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
if (authUser.getAuthority() != Authority.SYS_ADMIN) {
if (authUser.getTenantId() == null ||
!tenantId.getId().equals(ModelConstants.NULL_UUID) && !authUser.getTenantId().equals(tenantId)) {
throw new ThingsboardException(YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION,
ThingsboardErrorCode.PERMISSION_DENIED);
}
if (authUser.getAuthority() != Authority.TENANT_ADMIN ||
!authUser.getTenantId().equals(tenantId)) {
throw new ThingsboardException(YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION,
ThingsboardErrorCode.PERMISSION_DENIED);
}
return rule;
return ruleChain;
}
protected String constructBaseUrl(HttpServletRequest request) {
String scheme = request.getScheme();
if (request.getHeader("x-forwarded-proto") != null) {
@ -553,12 +536,127 @@ public abstract class BaseController {
protected <E extends BaseData<I> & HasName,
I extends UUIDBased & EntityId> void logEntityAction(I entityId, E entity, CustomerId customerId,
ActionType actionType, Exception e, Object... additionalInfo) throws ThingsboardException {
User user = getCurrentUser();
logEntityAction(getCurrentUser(), entityId, entity, customerId, actionType, e, additionalInfo);
}
protected <E extends BaseData<I> & HasName,
I extends UUIDBased & EntityId> void logEntityAction(User user, I entityId, E entity, CustomerId customerId,
ActionType actionType, Exception e, Object... additionalInfo) throws ThingsboardException {
if (customerId == null || customerId.isNullUid()) {
customerId = user.getCustomerId();
}
if (e == null) {
pushEntityActionToRuleEngine(entityId, entity, user, customerId, actionType, additionalInfo);
}
auditLogService.logEntityAction(user.getTenantId(), customerId, user.getId(), user.getName(), entityId, entity, actionType, e, additionalInfo);
}
public static Exception toException(Throwable error) {
return error != null ? (Exception.class.isInstance(error) ? (Exception) error : new Exception(error)) : null;
}
private <E extends BaseData<I> & HasName,
I extends UUIDBased & EntityId> void pushEntityActionToRuleEngine(I entityId, E entity, User user, CustomerId customerId,
ActionType actionType, Object... additionalInfo) {
String msgType = null;
switch (actionType) {
case ADDED:
msgType = DataConstants.ENTITY_CREATED;
break;
case DELETED:
msgType = DataConstants.ENTITY_DELETED;
break;
case UPDATED:
msgType = DataConstants.ENTITY_UPDATED;
break;
case ASSIGNED_TO_CUSTOMER:
msgType = DataConstants.ENTITY_ASSIGNED;
break;
case UNASSIGNED_FROM_CUSTOMER:
msgType = DataConstants.ENTITY_UNASSIGNED;
break;
case ATTRIBUTES_UPDATED:
msgType = DataConstants.ATTRIBUTES_UPDATED;
break;
case ATTRIBUTES_DELETED:
msgType = DataConstants.ATTRIBUTES_DELETED;
break;
}
if (!StringUtils.isEmpty(msgType)) {
try {
TbMsgMetaData metaData = new TbMsgMetaData();
metaData.putValue("userId", user.getId().toString());
metaData.putValue("userName", user.getName());
if (customerId != null && !customerId.isNullUid()) {
metaData.putValue("customerId", customerId.toString());
}
if (actionType == ActionType.ASSIGNED_TO_CUSTOMER) {
String strCustomerId = extractParameter(String.class, 1, additionalInfo);
String strCustomerName = extractParameter(String.class, 2, additionalInfo);
metaData.putValue("assignedCustomerId", strCustomerId);
metaData.putValue("assignedCustomerName", strCustomerName);
} else if (actionType == ActionType.UNASSIGNED_FROM_CUSTOMER) {
String strCustomerId = extractParameter(String.class, 1, additionalInfo);
String strCustomerName = extractParameter(String.class, 2, additionalInfo);
metaData.putValue("unassignedCustomerId", strCustomerId);
metaData.putValue("unassignedCustomerName", strCustomerName);
}
ObjectNode entityNode;
if (entity != null) {
entityNode = json.valueToTree(entity);
if (entityId.getEntityType() == EntityType.DASHBOARD) {
entityNode.put("configuration", "");
}
} else {
entityNode = json.createObjectNode();
if (actionType == ActionType.ATTRIBUTES_UPDATED) {
String scope = extractParameter(String.class, 0, additionalInfo);
List<AttributeKvEntry> attributes = extractParameter(List.class, 1, additionalInfo);
metaData.putValue("scope", scope);
if (attributes != null) {
for (AttributeKvEntry attr : attributes) {
if (attr.getDataType() == DataType.BOOLEAN) {
entityNode.put(attr.getKey(), attr.getBooleanValue().get());
} else if (attr.getDataType() == DataType.DOUBLE) {
entityNode.put(attr.getKey(), attr.getDoubleValue().get());
} else if (attr.getDataType() == DataType.LONG) {
entityNode.put(attr.getKey(), attr.getLongValue().get());
} else {
entityNode.put(attr.getKey(), attr.getValueAsString());
}
}
}
} else if (actionType == ActionType.ATTRIBUTES_DELETED) {
String scope = extractParameter(String.class, 0, additionalInfo);
List<String> keys = extractParameter(List.class, 1, additionalInfo);
metaData.putValue("scope", scope);
ArrayNode attrsArrayNode = entityNode.putArray("attributes");
if (keys != null) {
keys.forEach(attrsArrayNode::add);
}
}
}
TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), msgType, entityId, metaData, TbMsgDataType.JSON
, json.writeValueAsString(entityNode)
, null, null, 0L);
actorService.onMsg(new ServiceToRuleEngineMsg(user.getTenantId(), tbMsg));
} catch (Exception e) {
log.warn("[{}] Failed to push entity action to rule engine: {}", entityId, actionType, e);
}
}
}
private <T> T extractParameter(Class<T> clazz, int index, Object... additionalInfo) {
T result = null;
if (additionalInfo != null && additionalInfo.length > index) {
Object paramObject = additionalInfo[index];
if (clazz.isInstance(paramObject)) {
result = clazz.cast(paramObject);
}
}
return result;
}
}

23
application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java

@ -16,12 +16,19 @@
package org.thingsboard.server.controller;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.exception.ThingsboardException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@RestController
@RequestMapping("/api")
@ -52,12 +59,16 @@ public class ComponentDescriptorController extends BaseController {
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN','TENANT_ADMIN')")
@RequestMapping(value = "/components/actions/{pluginClazz:.+}", method = RequestMethod.GET)
@RequestMapping(value = "/components", params = {"componentTypes"}, method = RequestMethod.GET)
@ResponseBody
public List<ComponentDescriptor> getPluginActionsByPluginClazz(@PathVariable("pluginClazz") String pluginClazz) throws ThingsboardException {
checkParameter("pluginClazz", pluginClazz);
public List<ComponentDescriptor> getComponentDescriptorsByTypes(@RequestParam("componentTypes") String[] strComponentTypes) throws ThingsboardException {
checkArrayParameter("componentTypes", strComponentTypes);
try {
return checkPluginActionsByPluginClazz(pluginClazz);
Set<ComponentType> componentTypes = new HashSet<>();
for (String strComponentType : strComponentTypes) {
componentTypes.add(ComponentType.valueOf(strComponentType));
}
return checkComponentDescriptorsByTypes(componentTypes);
} catch (Exception e) {
throw handleException(e);
}

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

@ -20,15 +20,22 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
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.EntityType;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.CustomerId;
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.exception.ThingsboardException;
@RestController
@RequestMapping("/api")

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

@ -17,9 +17,21 @@ package org.thingsboard.server.controller;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.thingsboard.server.common.data.*;
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.Dashboard;
import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ShortCustomerInfo;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.TenantId;
@ -27,9 +39,6 @@ import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.exception.ThingsboardException;
import java.util.HashSet;
import java.util.Set;

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

@ -18,14 +18,22 @@ package org.thingsboard.server.controller;
import com.google.common.util.concurrent.ListenableFuture;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
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.Device;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.audit.ActionStatus;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.device.DeviceSearchQuery;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
@ -35,8 +43,6 @@ import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.exception.ThingsboardErrorCode;
import org.thingsboard.server.exception.ThingsboardException;
import org.thingsboard.server.service.security.model.SecurityUser;
import java.util.ArrayList;
@ -90,6 +96,11 @@ public class DeviceController extends BaseController {
savedDevice.getCustomerId(),
device.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
if (device.getId() == null) {
deviceStateService.onDeviceAdded(savedDevice);
} else {
deviceStateService.onDeviceUpdated(savedDevice);
}
return savedDevice;
} catch (Exception e) {
logEntityAction(emptyId(EntityType.DEVICE), device,
@ -112,6 +123,7 @@ public class DeviceController extends BaseController {
device.getCustomerId(),
ActionType.DELETED, null, strDeviceId);
deviceStateService.onDeviceDeleted(device);
} catch (Exception e) {
logEntityAction(emptyId(EntityType.DEVICE),
null,

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

@ -17,15 +17,21 @@ package org.thingsboard.server.controller;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
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.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntityRelationInfo;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
import org.thingsboard.server.exception.ThingsboardErrorCode;
import org.thingsboard.server.exception.ThingsboardException;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import java.util.List;

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

@ -17,15 +17,21 @@ package org.thingsboard.server.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.Event;
import org.thingsboard.server.common.data.id.*;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.exception.ThingsboardErrorCode;
import org.thingsboard.server.exception.ThingsboardException;
@RestController
@RequestMapping("/api")

32
application/src/main/java/org/thingsboard/server/controller/HttpValidationCallback.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.controller;
import com.google.common.util.concurrent.FutureCallback;
import org.springframework.http.ResponseEntity;
import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.server.service.security.ValidationCallback;
/**
* Created by ashvayka on 21.02.17.
*/
public class HttpValidationCallback extends ValidationCallback<DeferredResult<ResponseEntity>> {
public HttpValidationCallback(DeferredResult<ResponseEntity> response, FutureCallback<DeferredResult<ResponseEntity>> action) {
super(response, action);
}
}

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

Loading…
Cancel
Save