Browse Source

Merge remote-tracking branch 'origin/develop/1.5' into develop/cluter-refactoring

pull/773/head
Yura 8 years ago
parent
commit
711ff2ab53
  1. 2
      application/src/main/conf/thingsboard.conf
  2. 13
      application/src/main/data/json/demo/plugins/demo_device_messaging_rpc_plugin.json
  3. 28
      application/src/main/data/json/demo/plugins/demo_email_plugin.json
  4. 11
      application/src/main/data/json/demo/plugins/demo_time_rpc_plugin.json
  5. 46
      application/src/main/data/json/demo/rules/demo_alarm_rule.json
  6. 35
      application/src/main/data/json/demo/rules/demo_gettime_rpc_rule.json
  7. 38
      application/src/main/data/json/demo/rules/demo_messaging_rpc_rule.json
  8. 11
      application/src/main/data/json/system/plugins/system_rpc_plugin.json
  9. 9
      application/src/main/data/json/system/plugins/system_telemetry_plugin.json
  10. 29
      application/src/main/data/json/system/rules/system_telemetry_rule.json
  11. 98
      application/src/main/data/json/tenant/rule_chains/root_rule_chain.json
  12. 1
      application/src/main/data/upgrade/1.5.0/schema_update.cql
  13. 1
      application/src/main/data/upgrade/1.5.0/schema_update.sql
  14. 40
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  15. 72
      application/src/main/java/org/thingsboard/server/actors/app/AppActor.java
  16. 75
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java
  17. 409
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
  18. 38
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActorToRuleEngineMsg.java
  19. 40
      application/src/main/java/org/thingsboard/server/actors/device/PendingSessionMsgData.java
  20. 36
      application/src/main/java/org/thingsboard/server/actors/device/RuleEngineQueuePutAckMsg.java
  21. 4
      application/src/main/java/org/thingsboard/server/actors/device/ToDeviceRpcRequestMetadata.java
  22. 33
      application/src/main/java/org/thingsboard/server/actors/device/ToServerRpcRequestMetadata.java
  23. 5
      application/src/main/java/org/thingsboard/server/actors/plugin/PluginActor.java
  24. 9
      application/src/main/java/org/thingsboard/server/actors/plugin/PluginActorMessageProcessor.java
  25. 21
      application/src/main/java/org/thingsboard/server/actors/plugin/PluginProcessingContext.java
  26. 13
      application/src/main/java/org/thingsboard/server/actors/plugin/SharedPluginProcessingContext.java
  27. 23
      application/src/main/java/org/thingsboard/server/actors/plugin/ValidationResult.java
  28. 23
      application/src/main/java/org/thingsboard/server/actors/rpc/BasicRpcSessionListener.java
  29. 55
      application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
  30. 7
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActor.java
  31. 141
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java
  32. 10
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainManagerActor.java
  33. 40
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainToRuleChainMsg.java
  34. 1
      application/src/main/java/org/thingsboard/server/actors/service/ActorService.java
  35. 4
      application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java
  36. 11
      application/src/main/java/org/thingsboard/server/actors/session/ASyncMsgProcessor.java
  37. 26
      application/src/main/java/org/thingsboard/server/actors/session/AbstractSessionActorMsgProcessor.java
  38. 4
      application/src/main/java/org/thingsboard/server/actors/session/SyncMsgProcessor.java
  39. 27
      application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java
  40. 95
      application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
  41. 6
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  42. 8
      application/src/main/java/org/thingsboard/server/controller/DeviceController.java
  43. 225
      application/src/main/java/org/thingsboard/server/controller/RpcController.java
  44. 2
      application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
  45. 32
      application/src/main/java/org/thingsboard/server/controller/TenantController.java
  46. 23
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  47. 30
      application/src/main/java/org/thingsboard/server/service/cluster/rpc/ClusterGrpcService.java
  48. 9
      application/src/main/java/org/thingsboard/server/service/cluster/rpc/ClusterRpcService.java
  49. 6
      application/src/main/java/org/thingsboard/server/service/cluster/rpc/RpcMsgListener.java
  50. 33
      application/src/main/java/org/thingsboard/server/service/executors/ExternalCallExecutorService.java
  51. 9
      application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseSchemaService.java
  52. 12
      application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java
  53. 22
      application/src/main/java/org/thingsboard/server/service/install/DataUpdateService.java
  54. 106
      application/src/main/java/org/thingsboard/server/service/install/DefaultDataUpdateService.java
  55. 147
      application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
  56. 185
      application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java
  57. 9
      application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseSchemaService.java
  58. 12
      application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
  59. 4
      application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java
  60. 157
      application/src/main/java/org/thingsboard/server/service/rpc/DefaultDeviceRpcService.java
  61. 39
      application/src/main/java/org/thingsboard/server/service/rpc/DeviceRpcService.java
  62. 8
      application/src/main/java/org/thingsboard/server/service/rpc/LocalRequestMetaData.java
  63. 23
      application/src/main/java/org/thingsboard/server/service/rpc/ToDeviceRpcRequestActorMsg.java
  64. 60
      application/src/main/java/org/thingsboard/server/service/rpc/ToServerRpcResponseActorMsg.java
  65. 2
      application/src/main/java/org/thingsboard/server/service/script/NashornJsEngine.java
  66. 54
      application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java
  67. 390
      application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java
  68. 35
      application/src/main/java/org/thingsboard/server/service/state/DeviceState.java
  69. 37
      application/src/main/java/org/thingsboard/server/service/state/DeviceStateData.java
  70. 44
      application/src/main/java/org/thingsboard/server/service/state/DeviceStateService.java
  71. 47
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java
  72. 3
      application/src/main/proto/cluster.proto
  73. 33
      application/src/main/resources/thingsboard.yml
  74. 27
      application/src/test/java/org/thingsboard/server/actors/ActorsTestSuite.java
  75. 245
      application/src/test/java/org/thingsboard/server/actors/DefaultActorServiceTest.java
  76. 63
      application/src/test/java/org/thingsboard/server/actors/DummySessionID.java
  77. 21
      application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java
  78. 21
      application/src/test/java/org/thingsboard/server/controller/BaseComponentDescriptorControllerTest.java
  79. 232
      application/src/test/java/org/thingsboard/server/controller/BasePluginControllerTest.java
  80. 247
      application/src/test/java/org/thingsboard/server/controller/BaseRuleControllerTest.java
  81. 26
      application/src/test/java/org/thingsboard/server/controller/sql/PluginControllerSqlTest.java
  82. 1
      application/src/test/java/org/thingsboard/server/mqtt/rpc/sql/MqttServerSideRpcSqlIntegrationTest.java
  83. 42
      application/src/test/java/org/thingsboard/server/rules/RuleEngineNoSqlTestSuite.java
  84. 3
      application/src/test/java/org/thingsboard/server/rules/RuleEngineSqlTestSuite.java
  85. 140
      application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java
  86. 8
      application/src/test/java/org/thingsboard/server/rules/flow/nosql/RuleEngineFlowNoSqlIntegrationTest.java
  87. 3
      application/src/test/java/org/thingsboard/server/rules/flow/sql/RuleEngineFlowSqlIntegrationTest.java
  88. 76
      application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java
  89. 8
      application/src/test/java/org/thingsboard/server/rules/lifecycle/nosql/RuleEngineLifecycleNoSqlIntegrationTest.java
  90. 3
      application/src/test/java/org/thingsboard/server/rules/lifecycle/sql/RuleEngineLifecycleSqlIntegrationTest.java
  91. 14
      application/src/test/java/org/thingsboard/server/service/script/NashornJsEngineTest.java
  92. 1
      application/src/test/java/org/thingsboard/server/system/SystemSqlTestSuite.java
  93. 5
      common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java
  94. 2
      common/data/src/main/java/org/thingsboard/server/common/data/rpc/RpcRequest.java
  95. 2
      common/data/src/main/java/org/thingsboard/server/common/data/rpc/ToDeviceRpcRequestBody.java
  96. 4
      common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleNode.java
  97. 53
      common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java
  98. 45
      common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java
  99. 4
      common/message/src/main/java/org/thingsboard/server/common/msg/TbMsgMetaData.java
  100. 8
      common/message/src/main/java/org/thingsboard/server/common/msg/cluster/ClusterEventMsg.java

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": true,
"configuration": {
"defaultTTL": 0
}
},
{
"additionalInfo": {
"layoutX": 825,
"layoutY": 52
},
"type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode",
"name": "save client attributes",
"debugMode": true,
"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
}
}

1
application/src/main/data/upgrade/1.5.0/schema_update.cql

@ -84,6 +84,7 @@ CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.rule_chain_by_tenant_and_sear
CREATE TABLE IF NOT EXISTS thingsboard.rule_node (
id uuid,
rule_chain_id uuid,
type text,
name text,
debug_mode boolean,

1
application/src/main/data/upgrade/1.5.0/schema_update.sql

@ -28,6 +28,7 @@ CREATE TABLE IF NOT EXISTS rule_chain (
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),

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

@ -49,6 +49,7 @@ 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.queue.MsgQueue;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.rule.RuleService;
@ -60,8 +61,11 @@ 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.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.executors.ExternalCallExecutorService;
import org.thingsboard.server.service.mail.MailExecutorService;
import org.thingsboard.server.service.rpc.DeviceRpcService;
import org.thingsboard.server.service.script.JsExecutorService;
import org.thingsboard.server.service.state.DeviceStateService;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import java.io.IOException;
@ -161,6 +165,10 @@ public class ActorSystemContext {
@Getter
private TelemetrySubscriptionService tsSubService;
@Autowired
@Getter
private DeviceRpcService deviceRpcService;
@Autowired
@Getter
@Setter
@ -178,25 +186,37 @@ public class ActorSystemContext {
@Getter
private DbCallbackExecutorService dbCallbackExecutor;
@Autowired
@Getter
private ExternalCallExecutorService externalCallExecutorService;
@Autowired
@Getter
private MailService mailService;
@Autowired
@Getter
private MsgQueue msgQueue;
@Autowired
@Getter
private DeviceStateService deviceStateService;
@Value("${actors.session.sync.timeout}")
@Getter
private long syncSessionTimeout;
@Value("${actors.plugin.termination.delay}")
@Value("${actors.queue.enabled}")
@Getter
private long pluginActorTerminationDelay;
private boolean queuePersistenceEnabled;
@Value("${actors.plugin.processing.timeout}")
@Value("${actors.queue.timeout}")
@Getter
private long pluginProcessingTimeout;
private long queuePersistenceTimeout;
@Value("${actors.plugin.error_persist_frequency}")
@Value("${actors.client_side_rpc.timeout}")
@Getter
private long pluginErrorPersistFrequency;
private long clientSideRpcTimeout;
@Value("${actors.rule.chain.error_persist_frequency}")
@Getter
@ -206,14 +226,6 @@ public class ActorSystemContext {
@Getter
private long ruleNodeErrorPersistFrequency;
@Value("${actors.rule.termination.delay}")
@Getter
private long ruleActorTerminationDelay;
@Value("${actors.rule.error_persist_frequency}")
@Getter
private long ruleErrorPersistFrequency;
@Value("${actors.statistics.enabled}")
@Getter
private boolean statisticsEnabled;

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

@ -29,13 +29,12 @@ import org.thingsboard.server.actors.shared.plugin.SystemPluginManager;
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.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageDataIterable;
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.common.msg.aware.DeviceAwareMsg;
import org.thingsboard.server.common.msg.aware.TenantAwareMsg;
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;
@ -90,12 +89,23 @@ public class AppActor extends RuleChainManagerActor {
@Override
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_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;
default:
return false;
}
@ -110,48 +120,12 @@ public class AppActor extends RuleChainManagerActor {
}
}
// @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 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);
// }
// }
private void onPluginTerminated(PluginTerminationMsg msg) {
pluginManager.remove(msg.getId());
}
private void broadcast(Object msg) {
pluginManager.broadcast(msg);
@Override
protected void broadcast(Object msg) {
super.broadcast(msg);
tenantActors.values().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender()));
}
private void onToPluginMsg(ToPluginActorMsg msg) {
ActorRef target;
if (SYSTEM_TENANT.equals(msg.getPluginTenantId())) {
target = pluginManager.getOrCreateActor(this.context(), msg.getPluginId());
} else {
target = getOrCreateTenantActor(msg.getPluginTenantId());
}
target.tell(msg, ActorRef.noSender());
}
private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) {
ActorRef target;
if (SYSTEM_TENANT.equals(msg.getTenantId())) {
@ -166,17 +140,17 @@ public class AppActor extends RuleChainManagerActor {
}
}
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());
}
}

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

@ -24,59 +24,66 @@ 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.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.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.TimeoutMsg;
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg;
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
protected boolean process(TbActorMsg msg) {
return false;
}
@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);
if (msg instanceof ToDeviceActorMsg) {
processor.process(context(), (ToDeviceActorMsg) msg);
} else if (msg instanceof ToDeviceActorNotificationMsg) {
if (msg instanceof DeviceAttributesEventNotificationMsg) {
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> {

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

@ -18,30 +18,76 @@ 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.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.SessionId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKey;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
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.AttributesUpdateNotification;
import org.thingsboard.server.common.msg.core.AttributesUpdateRequest;
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.BasicToDeviceSessionActorMsg;
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.ToDeviceSessionActorMsg;
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.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.extensions.api.device.DeviceAttributes;
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.extensions.api.device.DeviceAttributesEventNotificationMsg;
import org.thingsboard.server.extensions.api.device.DeviceNameOrTypeUpdateMsg;
import org.thingsboard.server.extensions.api.plugins.msg.*;
import java.util.*;
import java.util.concurrent.ExecutionException;
import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse;
import org.thingsboard.server.extensions.api.plugins.msg.RpcError;
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.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;
@ -52,46 +98,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.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));
}
private void refreshAttributes(DeviceAttributesEventNotificationMsg msg) {
if (msg.isDeleted()) {
msg.getDeletedKeys().forEach(key -> deviceAttributes.remove(key));
} else {
deviceAttributes.update(msg.getScope(), msg.getValues());
}
this.defaultMetaData = new TbMsgMetaData();
this.defaultMetaData.putValue("deviceName", deviceName);
this.defaultMetaData.putValue("deviceType", deviceType);
}
void processRpcRequest(ActorContext context, ToDeviceRpcRequestPluginMsg msg) {
void processRpcRequest(ActorContext context, ToDeviceRpcRequestActorMsg msg) {
ToDeviceRpcRequest request = msg.getMsg();
ToDeviceRpcRequestBody body = request.getBody();
ToDeviceRpcRequestMsg rpcRequest = new ToDeviceRpcRequestMsg(
@ -118,9 +164,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().process(new FromDeviceRpcResponse(msg.getMsg().getId(), null, null));
} else {
registerPendingRpcRequest(context, msg, sent, rpcRequest, timeout);
}
@ -132,24 +177,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().process(new FromDeviceRpcResponse(requestMd.getMsg().getMsg().getId(),
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 BasicToDeviceSessionActorMsg(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 BasicToDeviceSessionActorMsg(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);
@ -159,12 +226,12 @@ 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) {
@ -173,8 +240,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
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().process(new FromDeviceRpcResponse(request.getId(), null, null));
}
ToDeviceRpcRequestMsg rpcRequest = new ToDeviceRpcRequestMsg(
entry.getKey(),
@ -186,14 +252,170 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
};
}
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 BasicToDeviceSessionActorMsg(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 BasicToDeviceSessionActorMsg(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, 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();
json.addProperty("ts", entry.getKey());
JsonObject values = new JsonObject();
for (KvEntry kv : entry.getValue()) {
kv.getBooleanValue().ifPresent(v -> values.addProperty(kv.getKey(), v));
kv.getLongValue().ifPresent(v -> values.addProperty(kv.getKey(), v));
kv.getDoubleValue().ifPresent(v -> values.addProperty(kv.getKey(), v));
kv.getStrValue().ifPresent(v -> values.addProperty(kv.getKey(), v));
}
json.add("values", values);
TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, defaultMetaData, 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 BasicToDeviceSessionActorMsg(toDeviceMsg, data.getSessionId()), data.getServer());
}
}
void processToServerRPCResponse(ActorContext context, ToServerRpcResponseActorMsg msg) {
ToServerRpcRequestMetadata data = toServerRpcPendingMap.remove(msg.getMsg().getRequestId());
if (data != null) {
sendMsgToSessionActor(new BasicToDeviceSessionActorMsg(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 {
ToDeviceSessionActorMsg response = new BasicToDeviceSessionActorMsg(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()) {
@ -223,50 +445,29 @@ 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().process(new FromDeviceRpcResponse(requestMd.getMsg().getMsg().getId(), 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());
? BasicCommandAckResponse.onSuccess(SessionMsgType.TO_DEVICE_RPC_REQUEST, responseMsg.getRequestId())
: BasicCommandAckResponse.onError(SessionMsgType.TO_DEVICE_RPC_REQUEST, responseMsg.getRequestId(), new TimeoutException());
sendMsgToSessionActor(new BasicToDeviceSessionActorMsg(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()
@ -276,69 +477,43 @@ 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);
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();
}
}
}
@ -352,17 +527,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
}
}
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() {
void processCredentialsUpdate() {
sessions.forEach((k, v) -> {
sendMsgToSessionActor(new BasicToDeviceSessionActorMsg(new SessionCloseNotification(), k), v.getServer());
});
@ -370,8 +535,12 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
rpcSubscriptions.clear();
}
public void processNameOrTypeUpdate(DeviceNameOrTypeUpdateMsg msg) {
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);
}
}

38
application/src/main/java/org/thingsboard/server/actors/device/DeviceActorToRuleEngineMsg.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.device;
import akka.actor.ActorRef;
import lombok.Data;
import org.thingsboard.server.common.data.id.TenantId;
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;
}
}

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;
}

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

@ -0,0 +1,33 @@
/**
* 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.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
*/
@Data
public class ToServerRpcRequestMetadata {
private final SessionId sessionId;
private final SessionType type;
private final Optional<ServerAddress> server;
}

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

@ -26,7 +26,7 @@ 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.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg;
import org.thingsboard.server.common.msg.timeout.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;
@ -153,6 +153,7 @@ public class PluginActor extends ComponentActor<PluginId, PluginActorMessageProc
@Override
protected long getErrorPersistFrequency() {
return systemContext.getPluginErrorPersistFrequency();
return 0;
// return systemContext.getPluginErrorPersistFrequency();
}
}

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

@ -30,13 +30,14 @@ 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.common.msg.session.SessionMsgType;
import org.thingsboard.server.common.msg.session.SessionMsgType;
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.common.msg.timeout.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;
@ -108,7 +109,7 @@ public class PluginActorMessageProcessor extends ComponentMsgProcessor<PluginId>
} catch (Exception ex) {
logger.debug("[{}] Failed to process RuleToPlugin msg: [{}] [{}]", tenantId, msg.getMsg(), ex);
RuleToPluginMsg ruleMsg = msg.getMsg();
MsgType responceMsgType = MsgType.RULE_ENGINE_ERROR;
SessionMsgType responceMsgType = SessionMsgType.RULE_ENGINE_ERROR;
Integer requestId = 0;
if (ruleMsg.getPayload() instanceof FromDeviceRequestMsg) {
requestId = ((FromDeviceRequestMsg) ruleMsg.getPayload()).getRequestId();
@ -216,7 +217,7 @@ public class PluginActorMessageProcessor extends ComponentMsgProcessor<PluginId>
@Override
public void onStop(ActorContext context) {
onStop();
scheduleMsgWithDelay(context, new PluginTerminationMsg(entityId), systemContext.getPluginActorTerminationDelay());
// scheduleMsgWithDelay(context, new PluginTerminationMsg(entityId), systemContext.getPluginActorTerminationDelay());
}
private void onStop() {

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

@ -36,9 +36,12 @@ 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.rpc.ToDeviceRpcRequestBody;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleMetaData;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
import org.thingsboard.server.common.msg.timeout.TimeoutMsg;
import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg;
import org.thingsboard.server.extensions.api.plugins.PluginApiCallSecurityContext;
import org.thingsboard.server.extensions.api.plugins.PluginCallback;
@ -348,7 +351,7 @@ public final class PluginProcessingContext implements PluginContext {
throw new IllegalStateException("Not Implemented!");
}
} else {
callback.onSuccess(this, ValidationResult.ok());
callback.onSuccess(this, ValidationResult.ok(null));
}
}
@ -366,7 +369,7 @@ public final class PluginProcessingContext implements PluginContext {
} else if (ctx.isCustomerUser() && !device.getCustomerId().equals(ctx.getCustomerId())) {
return ValidationResult.accessDenied("Device doesn't belong to the current Customer!");
} else {
return ValidationResult.ok();
return ValidationResult.ok(null);
}
}
}));
@ -387,7 +390,7 @@ public final class PluginProcessingContext implements PluginContext {
} else if (ctx.isCustomerUser() && !asset.getCustomerId().equals(ctx.getCustomerId())) {
return ValidationResult.accessDenied("Asset doesn't belong to the current Customer!");
} else {
return ValidationResult.ok();
return ValidationResult.ok(null);
}
}
}));
@ -408,7 +411,7 @@ public final class PluginProcessingContext implements PluginContext {
} else if (ctx.isSystemAdmin() && !rule.getTenantId().isNullUid()) {
return ValidationResult.accessDenied("Rule is not in system scope!");
} else {
return ValidationResult.ok();
return ValidationResult.ok(null);
}
}
}));
@ -429,7 +432,7 @@ public final class PluginProcessingContext implements PluginContext {
} else if (ctx.isSystemAdmin() && !ruleChain.getTenantId().isNullUid()) {
return ValidationResult.accessDenied("Rule chain is not in system scope!");
} else {
return ValidationResult.ok();
return ValidationResult.ok(null);
}
}
}));
@ -451,7 +454,7 @@ public final class PluginProcessingContext implements PluginContext {
} else if (ctx.isSystemAdmin() && !plugin.getTenantId().isNullUid()) {
return ValidationResult.accessDenied("Plugin is not in system scope!");
} else {
return ValidationResult.ok();
return ValidationResult.ok(null);
}
}
}));
@ -472,7 +475,7 @@ public final class PluginProcessingContext implements PluginContext {
} 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();
return ValidationResult.ok(null);
}
}
}));
@ -483,7 +486,7 @@ public final class PluginProcessingContext implements PluginContext {
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());
callback.onSuccess(this, ValidationResult.ok(null));
} else {
ListenableFuture<Tenant> tenantFuture = pluginCtx.tenantService.findTenantByIdAsync(new TenantId(entityId.getId()));
Futures.addCallback(tenantFuture, getCallback(callback, tenant -> {
@ -492,7 +495,7 @@ public final class PluginProcessingContext implements PluginContext {
} else if (!tenant.getId().equals(ctx.getTenantId())) {
return ValidationResult.accessDenied("Tenant doesn't relate to the currently authorized user!");
} else {
return ValidationResult.ok();
return ValidationResult.ok(null);
}
}));
}

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

@ -18,13 +18,13 @@ 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.PluginId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
import org.thingsboard.server.common.msg.timeout.TimeoutMsg;
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;
@ -37,9 +37,6 @@ 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;
@ -108,8 +105,8 @@ public final class SharedPluginProcessingContext {
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);
// 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) {

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

@ -20,29 +20,30 @@ import lombok.Data;
@Data
@AllArgsConstructor
public class ValidationResult {
public class ValidationResult<V> {
private final ValidationResultCode resultCode;
private final String message;
private final V v;
public static ValidationResult ok() {
return new ValidationResult(ValidationResultCode.OK, "Ok");
public static <V> ValidationResult<V> ok(V v) {
return new ValidationResult<>(ValidationResultCode.OK, "Ok", v);
}
public static ValidationResult accessDenied(String message) {
return new ValidationResult(ValidationResultCode.ACCESS_DENIED, message);
public static <V> ValidationResult<V> accessDenied(String message) {
return new ValidationResult<>(ValidationResultCode.ACCESS_DENIED, message, null);
}
public static ValidationResult entityNotFound(String message) {
return new ValidationResult(ValidationResultCode.ENTITY_NOT_FOUND, message);
public static <V> ValidationResult<V> entityNotFound(String message) {
return new ValidationResult<>(ValidationResultCode.ENTITY_NOT_FOUND, message, null);
}
public static ValidationResult unauthorized(String message) {
return new ValidationResult(ValidationResultCode.UNAUTHORIZED, message);
public static <V> ValidationResult<V> unauthorized(String message) {
return new ValidationResult<>(ValidationResultCode.UNAUTHORIZED, message, null);
}
public static ValidationResult internalError(String message) {
return new ValidationResult(ValidationResultCode.INTERNAL_ERROR, message);
public static <V> ValidationResult<V> internalError(String message) {
return new ValidationResult<>(ValidationResultCode.INTERNAL_ERROR, message, null);
}
}

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

@ -24,10 +24,12 @@ 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.data.rpc.ToDeviceRpcRequestBody;
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.common.msg.device.DeviceToDeviceActorMsg;
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
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;
@ -35,6 +37,7 @@ 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 org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg;
import java.io.Serializable;
import java.util.UUID;
@ -83,7 +86,7 @@ public class BasicRpcSessionListener implements GrpcSessionListener {
@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()));
service.onMsg((DeviceToDeviceActorMsg) deserialize(msg.getData().toByteArray()));
}
@Override
@ -139,28 +142,20 @@ public class BasicRpcSessionListener implements GrpcSessionListener {
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()));
private static ToDeviceRpcRequestActorMsg deserialize(ServerAddress serverAddress, ClusterAPIProtos.ToDeviceRpcRequestRpcMessage msg) {
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);
ToDeviceRpcRequest request = new ToDeviceRpcRequest(toUUID(msg.getMsgId()), deviceTenantId, deviceId, msg.getOneway(), msg.getExpTime(), requestBody);
return new ToDeviceRpcRequestPluginMsg(serverAddress, pluginId, pluginTenantId, request);
return new ToDeviceRpcRequestActorMsg(serverAddress, 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);
return new ToPluginRpcResponseDeviceMsg(null, null, response);
}
@SuppressWarnings("unchecked")

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

@ -16,15 +16,20 @@
package org.thingsboard.server.actors.ruleChain;
import akka.actor.ActorRef;
import akka.actor.Cancellable;
import com.datastax.driver.core.utils.UUIDs;
import com.google.common.base.Function;
import org.thingsboard.rule.engine.api.*;
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.cluster.ServerAddress;
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;
@ -41,13 +46,13 @@ import scala.concurrent.duration.Duration;
import java.util.List;
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 static final Function<? super List<Void>, ? extends Void> LIST_VOID_FUNCTION = v -> null;
private final ActorSystemContext mainCtx;
private final RuleNodeCtx nodeCtx;
@ -63,8 +68,13 @@ class DefaultTbContext implements TbContext {
@Override
public void tellNext(TbMsg msg, String relationType) {
tellNext(msg, relationType, null);
}
@Override
public void tellNext(TbMsg msg, String relationType, Throwable th) {
if (nodeCtx.getSelf().isDebugMode()) {
mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg);
mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg, th);
}
nodeCtx.getChainActor().tell(new RuleNodeToRuleChainTellNextMsg(nodeCtx.getSelf().getId(), relationType, msg), nodeCtx.getSelfActor());
}
@ -112,6 +122,16 @@ class DefaultTbContext implements TbContext {
nodeCtx.setSelf(self);
}
@Override
public TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, String data) {
return new TbMsg(UUIDs.timeBased(), type, originator, metaData, data, nodeCtx.getSelf().getRuleChainId(), nodeCtx.getSelf().getId(), 0L);
}
@Override
public TbMsg transformMsg(TbMsg origMsg, String type, EntityId originator, TbMsgMetaData metaData, String data) {
return new TbMsg(origMsg.getId(), type, originator, metaData, data, nodeCtx.getSelf().getRuleChainId(), nodeCtx.getSelf().getId(), 0L);
}
@Override
public RuleNodeId getSelfId() {
return nodeCtx.getSelf().getId();
@ -142,6 +162,11 @@ class DefaultTbContext implements TbContext {
return mainCtx.getDbCallbackExecutor();
}
@Override
public ListeningExecutor getExternalCallExecutor() {
return mainCtx.getExternalCallExecutorService();
}
@Override
public ScriptEngine createJsScriptEngine(String script, String functionName, String... argNames) {
return new NashornJsEngine(script, functionName, argNames);
@ -206,4 +231,28 @@ class DefaultTbContext implements TbContext {
public MailService getMailService() {
return mainCtx.getMailService();
}
@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().process(request, response -> {
consumer.accept(RuleEngineDeviceRpcResponse.builder()
.deviceId(src.getDeviceId())
.requestId(src.getRequestId())
.error(response.getError())
.response(response.getResponse())
.build());
});
}
};
}
}

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

@ -18,6 +18,7 @@ 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;
@ -44,9 +45,15 @@ public class RuleChainActor extends ComponentActor<RuleChainId, RuleChainActorMe
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;
default:
return false;
}

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

@ -19,7 +19,10 @@ 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;
@ -39,6 +42,7 @@ 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;
@ -50,6 +54,7 @@ import java.util.stream.Collectors;
*/
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;
@ -58,6 +63,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
private RuleNodeId firstId;
private RuleNodeCtx firstNode;
private boolean started;
RuleChainActorMessageProcessor(TenantId tenantId, RuleChainId ruleChainId, ActorSystemContext systemContext
, LoggingAdapter logger, ActorRef parent, ActorRef self) {
@ -71,14 +77,33 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
@Override
public void start(ActorContext context) throws Exception {
RuleChain ruleChain = service.findRuleChainById(entityId);
List<RuleNode> ruleNodeList = service.getRuleChainNodes(entityId);
// Creating and starting the actors;
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) {
ActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode);
nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode));
for (TbMsg tbMsg : queue.findUnprocessed(ruleNode.getId().getId(), 0L)) {
pushMsgToNode(nodeActors.get(ruleNode.getId()), tbMsg);
}
}
if (firstNode != null) {
for (TbMsg tbMsg : queue.findUnprocessed(entityId.getId(), 0L)) {
pushMsgToNode(firstNode, tbMsg);
}
}
initRoutes(ruleChain, ruleNodeList);
}
@Override
@ -105,6 +130,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
});
initRoutes(ruleChain, ruleNodeList);
reprocess(ruleNodeList);
}
@Override
@ -113,6 +139,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
nodeActors.clear();
nodeRoutes.clear();
context.stop(self);
started = false;
}
@Override
@ -133,15 +160,19 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
// Populating the routes map;
for (RuleNode ruleNode : ruleNodeList) {
List<EntityRelation> relations = service.getRuleNodeRelations(ruleNode.getId());
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() + "]");
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()));
}
nodeRoutes.computeIfAbsent(ruleNode.getId(), k -> new ArrayList<>())
.add(new RuleNodeRelation(ruleNode.getId(), relation.getTo(), relation.getType()));
}
}
@ -152,37 +183,81 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
void onServiceToRuleEngineMsg(ServiceToRuleEngineMsg envelope) {
checkActive();
TbMsg tbMsg = envelope.getTbMsg();
//TODO: push to queue and act on ack in async way
pushMsgToNode(firstNode, tbMsg);
putToQueue(enrichWithRuleChainId(envelope.getTbMsg()), msg -> pushMsgToNode(firstNode, msg));
}
void onDeviceActorToRuleEngineMsg(DeviceActorToRuleEngineMsg envelope) {
checkActive();
putToQueue(enrichWithRuleChainId(envelope.getTbMsg()), msg -> {
pushMsgToNode(firstNode, msg);
envelope.getCallbackRef().tell(new RuleEngineQueuePutAckMsg(msg.getId()), self);
});
}
void onRuleChainToRuleChainMsg(RuleChainToRuleChainMsg envelope) {
checkActive();
if (envelope.isEnqueue()) {
putToQueue(enrichWithRuleChainId(envelope.getMsg()), msg -> pushMsgToNode(firstNode, msg));
} else {
pushMsgToNode(firstNode, envelope.getMsg());
}
}
void onTellNext(RuleNodeToRuleChainTellNextMsg envelope) {
checkActive();
RuleNodeId originator = envelope.getOriginator();
String targetRelationType = envelope.getRelationType();
List<RuleNodeRelation> relations = nodeRoutes.get(originator);
if (relations == null) {
return;
}
boolean copy = relations.size() > 1;
for (RuleNodeRelation relation : relations) {
TbMsg msg = envelope.getMsg();
if (copy) {
msg = msg.copy();
List<RuleNodeRelation> relations = nodeRoutes.get(originator).stream()
.filter(r -> targetRelationType == null || targetRelationType.equalsIgnoreCase(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(msg, ackId.getId(), msg.getClusterPartition());
} else if (relationsCount == 1) {
for (RuleNodeRelation relation : relations) {
pushToTarget(msg, relation.getOut());
}
if (targetRelationType == null || targetRelationType.equalsIgnoreCase(relation.getType())) {
switch (relation.getOut().getEntityType()) {
} else {
for (RuleNodeRelation relation : relations) {
EntityId target = relation.getOut();
switch (target.getEntityType()) {
case RULE_NODE:
RuleNodeId targetRuleNodeId = new RuleNodeId(relation.getOut().getId());
RuleNodeCtx targetRuleNode = nodeActors.get(targetRuleNodeId);
pushMsgToNode(targetRuleNode, msg);
enqueueAndForwardMsgCopyToNode(msg, target);
break;
case RULE_CHAIN:
// TODO: implement
enqueueAndForwardMsgCopyToChain(msg, target);
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(msg, ackId.getId(), msg.getClusterPartition());
}
}
private void enqueueAndForwardMsgCopyToChain(TbMsg msg, EntityId target) {
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, true), self);
}
private void enqueueAndForwardMsgCopyToNode(TbMsg msg, EntityId target) {
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));
}
private void pushToTarget(TbMsg msg, EntityId target) {
switch (target.getEntityType()) {
case RULE_NODE:
pushMsgToNode(nodeActors.get(new RuleNodeId(target.getId())), msg);
break;
case RULE_CHAIN:
parent.tell(new RuleChainToRuleChainMsg(new RuleChainId(target.getId()), entityId, msg, false), self);
break;
}
}
@ -192,4 +267,8 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
}
}
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(), tbMsg.getData(), entityId, null, 0L);
}
}

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

@ -31,31 +31,29 @@ import org.thingsboard.server.dao.rule.RuleChainService;
public abstract class RuleChainManagerActor extends ContextAwareActor {
protected final RuleChainManager ruleChainManager;
protected final PluginManager pluginManager;
protected final RuleChainService ruleChainService;
public RuleChainManagerActor(ActorSystemContext systemContext, RuleChainManager ruleChainManager, PluginManager pluginManager) {
super(systemContext);
this.ruleChainManager = ruleChainManager;
this.pluginManager = pluginManager;
this.ruleChainService = systemContext.getRuleChainService();
}
protected void initRuleChains() {
pluginManager.init(this.context());
ruleChainManager.init(this.context());
}
protected ActorRef getEntityActorRef(EntityId entityId) {
ActorRef target = null;
switch (entityId.getEntityType()) {
case PLUGIN:
target = pluginManager.getOrCreateActor(this.context(), (PluginId) entityId);
break;
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.rule.engine.api.TbContext;
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 boolean enqueue;
@Override
public MsgType getMsgType() {
return MsgType.RULE_CHAIN_TO_RULE_CHAIN_MSG;
}
}

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

@ -32,4 +32,5 @@ public interface ActorService extends SessionMsgProcessor, WebSocketMsgProcessor
void onCredentialsUpdate(TenantId tenantId, DeviceId deviceId);
void onDeviceNameOrTypeUpdate(TenantId tenantId, DeviceId deviceId, String deviceName, String deviceType);
}

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

@ -39,7 +39,7 @@ import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg;
import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg;
import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
import org.thingsboard.server.extensions.api.device.DeviceNameOrTypeUpdateMsg;
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.extensions.api.device.DeviceCredentialsUpdateNotificationMsg;
import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
@ -156,7 +156,7 @@ public class DefaultActorService implements ActorService {
}
@Override
public void onMsg(ToDeviceActorMsg msg) {
public void onMsg(DeviceToDeviceActorMsg msg) {
log.trace("Processing device rpc msg: {}", msg);
appActor.tell(msg, ActorRef.noSender());
}

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

@ -22,12 +22,11 @@ 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.SessionCloseMsg;
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
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.session.ex.SessionException;
import java.util.HashMap;
@ -37,7 +36,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;
@ -53,7 +52,7 @@ class ASyncMsgProcessor extends AbstractSessionActorMsgProcessor {
toDeviceMsg(new SessionOpenMsg()).ifPresent(m -> forwardToAppActor(ctx, m));
firstMsg = false;
}
ToDeviceActorMsg pendingMsg = toDeviceMsg(msg);
DeviceToDeviceActorMsg pendingMsg = toDeviceMsg(msg);
FromDeviceMsg fromDeviceMsg = pendingMsg.getPayload();
switch (fromDeviceMsg.getMsgType()) {
case POST_TELEMETRY_REQUEST:
@ -86,8 +85,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;

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

@ -22,8 +22,8 @@ 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.ServerAddress;
import org.thingsboard.server.common.msg.device.BasicToDeviceActorMsg;
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
import org.thingsboard.server.common.msg.device.BasicDeviceToDeviceActorMsg;
import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
import org.thingsboard.server.common.msg.session.*;
import org.thingsboard.server.common.msg.session.ctrl.SessionCloseMsg;
@ -37,7 +37,7 @@ abstract class AbstractSessionActorMsgProcessor extends AbstractContextAwareMsgP
protected final SessionId sessionId;
protected SessionContext sessionCtx;
protected ToDeviceActorMsg toDeviceActorMsgPrototype;
protected DeviceToDeviceActorMsg deviceToDeviceActorMsgPrototype;
protected AbstractSessionActorMsgProcessor(ActorSystemContext ctx, LoggingAdapter logger, SessionId sessionId) {
super(ctx, logger);
@ -64,29 +64,29 @@ abstract class AbstractSessionActorMsgProcessor extends AbstractContextAwareMsgP
protected void updateSessionCtx(ToDeviceActorSessionMsg 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(ToDeviceActorSessionMsg 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> forwardToAppActorIfAdressChanged(ActorContext ctx, DeviceToDeviceActorMsg toForward, Optional<ServerAddress> oldAddress) {
Optional<ServerAddress> newAddress = systemContext.getRoutingService().resolveById(toForward.getDeviceId());
if (!newAddress.equals(oldAddress)) {
if (newAddress.isPresent()) {
@ -99,7 +99,7 @@ abstract class AbstractSessionActorMsgProcessor extends AbstractContextAwareMsgP
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()));
@ -114,6 +114,6 @@ abstract class AbstractSessionActorMsgProcessor extends AbstractContextAwareMsgP
}
public DeviceId getDeviceId() {
return toDeviceActorMsgPrototype.getDeviceId();
return deviceToDeviceActorMsgPrototype.getDeviceId();
}
}

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

@ -20,7 +20,7 @@ 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.device.DeviceToDeviceActorMsg;
import org.thingsboard.server.common.msg.session.*;
import org.thingsboard.server.common.msg.session.ToDeviceActorSessionMsg;
import org.thingsboard.server.common.msg.session.ctrl.SessionCloseMsg;
@ -32,7 +32,7 @@ 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;

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

@ -17,22 +17,32 @@ 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.dao.queue.MsgQueue;
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 MsgQueue 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.getMsgQueue();
}
public abstract void start(ActorContext context) throws Exception;
@ -75,4 +85,19 @@ public abstract class ComponentMsgProcessor<T> extends AbstractContextAwareMsgPr
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(tbMsg, entityId.getId(), 0), 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);
}
});
}
}

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

@ -16,13 +16,16 @@
package org.thingsboard.server.actors.tenant;
import akka.actor.ActorRef;
import akka.actor.OneForOneStrategy;
import akka.actor.Props;
import akka.event.Logging;
import akka.event.LoggingAdapter;
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.device.DeviceActorToRuleEngineMsg;
import org.thingsboard.server.actors.plugin.PluginTerminationMsg;
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.TenantPluginManager;
@ -30,11 +33,13 @@ import org.thingsboard.server.actors.shared.rulechain.TenantRuleChainManager;
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.device.ToDeviceActorMsg;
import org.thingsboard.server.common.msg.aware.DeviceAwareMsg;
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.extensions.api.device.ToDeviceActorNotificationMsg;
import org.thingsboard.server.extensions.api.plugins.msg.ToPluginActorMsg;
import scala.concurrent.duration.Duration;
import java.util.HashMap;
import java.util.Map;
@ -50,6 +55,12 @@ public class TenantActor extends RuleChainManagerActor {
this.deviceActors = new HashMap<>();
}
@Override
public SupervisorStrategy supervisorStrategy() {
return strategy;
}
@Override
public void preStart() {
logger.info("[{}] Starting tenant actor.", tenantId);
@ -64,63 +75,56 @@ public class TenantActor extends RuleChainManagerActor {
@Override
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;
}
@Override
protected void broadcast(Object msg) {
super.broadcast(msg);
deviceActors.values().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender()));
}
private void onServiceToRuleEngineMsg(ServiceToRuleEngineMsg msg) {
ruleChainManager.getRootChainActor().tell(msg, self());
}
// @Override
// public void onReceive(Object msg) throws Exception {
// logger.debug("[{}] Received message: {}", tenantId, msg);
// if (msg instanceof ToDeviceActorMsg) {
// onToDeviceActorMsg((ToDeviceActorMsg) msg);
// } else if (msg instanceof ToPluginActorMsg) {
// onToPluginMsg((ToPluginActorMsg) 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);
// }
// }
private void broadcast(Object msg) {
pluginManager.broadcast(msg);
deviceActors.values().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender()));
private void onDeviceActorToRuleEngineMsg(DeviceActorToRuleEngineMsg msg) {
ruleChainManager.getRootChainActor().tell(msg, self());
}
private void onToDeviceActorMsg(ToDeviceActorMsg msg) {
getOrCreateDeviceActor(msg.getDeviceId()).tell(msg, ActorRef.noSender());
private void onRuleChainMsg(RuleChainToRuleChainMsg msg) {
ruleChainManager.getOrCreateActor(context(), msg.getTarget()).tell(msg, self());
}
private void onToDeviceActorMsg(ToDeviceActorNotificationMsg msg) {
getOrCreateDeviceActor(msg.getDeviceId()).tell(msg, ActorRef.noSender());
}
private void onToPluginMsg(ToPluginActorMsg msg) {
if (msg.getPluginTenantId().equals(tenantId)) {
ActorRef pluginActor = pluginManager.getOrCreateActor(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) {
@ -132,11 +136,6 @@ public class TenantActor extends RuleChainManagerActor {
}
}
private void onPluginTerminated(PluginTerminationMsg msg) {
pluginManager.remove(msg.getId());
}
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()));
@ -158,4 +157,12 @@ public class TenantActor extends RuleChainManagerActor {
}
}
private final SupervisorStrategy strategy = new OneForOneStrategy(3, Duration.create("1 minute"), new Function<Throwable, SupervisorStrategy.Directive>() {
@Override
public SupervisorStrategy.Directive apply(Throwable t) {
logger.error(t, "Unknown failure");
return SupervisorStrategy.resume();
}
});
}

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

@ -63,6 +63,7 @@ import org.thingsboard.server.exception.ThingsboardErrorResponseHandler;
import org.thingsboard.server.common.data.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;
@ -137,6 +138,9 @@ public abstract class BaseController {
@Autowired
protected DeviceOfflineService offlineService;
@Autowired
protected DeviceStateService deviceStateService;
@ExceptionHandler(ThingsboardException.class)
public void handleThingsboardException(ThingsboardException ex, HttpServletResponse response) {
errorResponseHandler.handle(ex, response);
@ -606,7 +610,7 @@ public abstract class BaseController {
auditLogService.logEntityAction(user.getTenantId(), customerId, user.getId(), user.getName(), entityId, entity, actionType, e, additionalInfo);
}
protected static Exception toException(Throwable error) {
public static Exception toException(Throwable error) {
return Exception.class.isInstance(error) ? (Exception) error : new Exception(error);
}

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

@ -90,6 +90,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 +117,7 @@ public class DeviceController extends BaseController {
device.getCustomerId(),
ActionType.DELETED, null, strDeviceId);
deviceStateService.onDeviceDeleted(device);
} catch (Exception e) {
logEntityAction(emptyId(EntityType.DEVICE),
null,
@ -387,7 +393,7 @@ public class DeviceController extends BaseController {
@RequestMapping(value = "/device/online", method = RequestMethod.GET)
@ResponseBody
public List<Device> getOnlineDevices(@RequestParam("contactType") DeviceStatusQuery.ContactType contactType,
@RequestParam("threshold") long threshold) throws ThingsboardException {
@RequestParam("threshold") long threshold) throws ThingsboardException {
try {
TenantId tenantId = getCurrentUser().getTenantId();
ListenableFuture<List<Device>> offlineDevices = offlineService.findOnlineDevices(tenantId.getId(), contactType, threshold);

225
application/src/main/java/org/thingsboard/server/controller/RpcController.java

@ -0,0 +1,225 @@
/**
* 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.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.util.concurrent.FutureCallback;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
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.DeviceId;
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.rpc.RpcRequest;
import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
import org.thingsboard.server.extensions.api.exception.ToErrorResponseEntity;
import org.thingsboard.server.extensions.api.plugins.PluginConstants;
import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse;
import org.thingsboard.server.extensions.api.plugins.msg.RpcError;
import org.thingsboard.server.service.rpc.DeviceRpcService;
import org.thingsboard.server.service.rpc.LocalRequestMetaData;
import org.thingsboard.server.service.security.AccessValidator;
import org.thingsboard.server.service.security.model.SecurityUser;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.IOException;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
/**
* Created by ashvayka on 22.03.18.
*/
@RestController
@RequestMapping(PluginConstants.RPC_URL_PREFIX)
@Slf4j
public class RpcController extends BaseController {
public static final int DEFAULT_TIMEOUT = 10000;
protected final ObjectMapper jsonMapper = new ObjectMapper();
@Autowired
private DeviceRpcService deviceRpcService;
@Autowired
private AccessValidator accessValidator;
private ExecutorService executor;
@PostConstruct
public void initExecutor() {
executor = Executors.newSingleThreadExecutor();
}
@PreDestroy
public void shutdownExecutor() {
if (executor != null) {
executor.shutdownNow();
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/oneway/{deviceId}", method = RequestMethod.POST)
@ResponseBody
public DeferredResult<ResponseEntity> handleOneWayDeviceRPCRequest(@PathVariable("deviceId") String deviceIdStr, @RequestBody String requestBody) throws ThingsboardException {
return handleDeviceRPCRequest(true, new DeviceId(UUID.fromString(deviceIdStr)), requestBody);
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/twoway/{deviceId}", method = RequestMethod.POST)
@ResponseBody
public DeferredResult<ResponseEntity> handleTwoWayDeviceRPCRequest(@PathVariable("deviceId") String deviceIdStr, @RequestBody String requestBody) throws ThingsboardException {
return handleDeviceRPCRequest(false, new DeviceId(UUID.fromString(deviceIdStr)), requestBody);
}
private DeferredResult<ResponseEntity> handleDeviceRPCRequest(boolean oneWay, DeviceId deviceId, String requestBody) throws ThingsboardException {
try {
JsonNode rpcRequestBody = jsonMapper.readTree(requestBody);
RpcRequest cmd = new RpcRequest(rpcRequestBody.get("method").asText(),
jsonMapper.writeValueAsString(rpcRequestBody.get("params")));
if (rpcRequestBody.has("timeout")) {
cmd.setTimeout(rpcRequestBody.get("timeout").asLong());
}
SecurityUser currentUser = getCurrentUser();
TenantId tenantId = currentUser.getTenantId();
final DeferredResult<ResponseEntity> response = new DeferredResult<>();
long timeout = System.currentTimeMillis() + (cmd.getTimeout() != null ? cmd.getTimeout() : DEFAULT_TIMEOUT);
ToDeviceRpcRequestBody body = new ToDeviceRpcRequestBody(cmd.getMethodName(), cmd.getRequestData());
accessValidator.validate(currentUser, deviceId, new HttpValidationCallback(response, new FutureCallback<DeferredResult<ResponseEntity>>() {
@Override
public void onSuccess(@Nullable DeferredResult<ResponseEntity> result) {
ToDeviceRpcRequest rpcRequest = new ToDeviceRpcRequest(UUID.randomUUID(),
tenantId,
deviceId,
oneWay,
timeout,
body
);
deviceRpcService.process(rpcRequest, new Consumer<FromDeviceRpcResponse>(){
@Override
public void accept(FromDeviceRpcResponse fromDeviceRpcResponse) {
reply(new LocalRequestMetaData(rpcRequest, currentUser, result), fromDeviceRpcResponse);
}
});
}
@Override
public void onFailure(Throwable e) {
ResponseEntity entity;
if (e instanceof ToErrorResponseEntity) {
entity = ((ToErrorResponseEntity) e).toErrorResponseEntity();
} else {
entity = new ResponseEntity(HttpStatus.UNAUTHORIZED);
}
logRpcCall(currentUser, deviceId, body, oneWay, Optional.empty(), e);
response.setResult(entity);
}
}));
return response;
} catch (IOException ioe) {
throw new ThingsboardException("Invalid request body", ioe, ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
}
public void reply(LocalRequestMetaData rpcRequest, FromDeviceRpcResponse response) {
Optional<RpcError> rpcError = response.getError();
DeferredResult<ResponseEntity> responseWriter = rpcRequest.getResponseWriter();
if (rpcError.isPresent()) {
logRpcCall(rpcRequest, rpcError, null);
RpcError error = rpcError.get();
switch (error) {
case TIMEOUT:
responseWriter.setResult(new ResponseEntity<>(HttpStatus.REQUEST_TIMEOUT));
break;
case NO_ACTIVE_CONNECTION:
responseWriter.setResult(new ResponseEntity<>(HttpStatus.CONFLICT));
break;
default:
responseWriter.setResult(new ResponseEntity<>(HttpStatus.REQUEST_TIMEOUT));
break;
}
} else {
Optional<String> responseData = response.getResponse();
if (responseData.isPresent() && !StringUtils.isEmpty(responseData.get())) {
String data = responseData.get();
try {
logRpcCall(rpcRequest, rpcError, null);
responseWriter.setResult(new ResponseEntity<>(jsonMapper.readTree(data), HttpStatus.OK));
} catch (IOException e) {
log.debug("Failed to decode device response: {}", data, e);
logRpcCall(rpcRequest, rpcError, e);
responseWriter.setResult(new ResponseEntity<>(HttpStatus.NOT_ACCEPTABLE));
}
} else {
logRpcCall(rpcRequest, rpcError, null);
responseWriter.setResult(new ResponseEntity<>(HttpStatus.OK));
}
}
}
private void logRpcCall(LocalRequestMetaData rpcRequest, Optional<RpcError> rpcError, Throwable e) {
logRpcCall(rpcRequest.getUser(), rpcRequest.getRequest().getDeviceId(), rpcRequest.getRequest().getBody(), rpcRequest.getRequest().isOneway(), rpcError, null);
}
private void logRpcCall(SecurityUser user, EntityId entityId, ToDeviceRpcRequestBody body, boolean oneWay, Optional<RpcError> rpcError, Throwable e) {
String rpcErrorStr = "";
if (rpcError.isPresent()) {
rpcErrorStr = "RPC Error: " + rpcError.get().name();
}
String method = body.getMethod();
String params = body.getParams();
auditLogService.logEntityAction(
user.getTenantId(),
user.getCustomerId(),
user.getId(),
user.getName(),
(UUIDBased & EntityId) entityId,
null,
ActionType.RPC_CALL,
BaseController.toException(e),
rpcErrorStr,
oneWay,
method,
params);
}
}

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

@ -237,7 +237,7 @@ public class RuleChainController extends BaseController {
ScriptEngine engine = null;
try {
engine = new NashornJsEngine(script, functionName, argNames);
TbMsg inMsg = new TbMsg(UUIDs.timeBased(), msgType, null, new TbMsgMetaData(metadata), data);
TbMsg inMsg = new TbMsg(UUIDs.timeBased(), msgType, null, new TbMsgMetaData(metadata), data, null, null, 0L);
switch (scriptType) {
case "update":
output = msgToOutput(engine.executeUpdate(inMsg));

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

@ -15,21 +15,34 @@
*/
package org.thingsboard.server.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
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.Tenant;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.service.install.InstallScripts;
@RestController
@RequestMapping("/api")
@Slf4j
public class TenantController extends BaseController {
@Autowired
private InstallScripts installScripts;
@Autowired
private TenantService tenantService;
@ -49,10 +62,15 @@ public class TenantController extends BaseController {
@PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/tenant", method = RequestMethod.POST)
@ResponseBody
@ResponseBody
public Tenant saveTenant(@RequestBody Tenant tenant) throws ThingsboardException {
try {
return checkNotNull(tenantService.saveTenant(tenant));
boolean newTenant = tenant.getId() == null;
tenant = checkNotNull(tenantService.saveTenant(tenant));
if (newTenant) {
installScripts.createDefaultRuleChains(tenant.getId());
}
return tenant;
} catch (Exception e) {
throw handleException(e);
}
@ -72,7 +90,7 @@ public class TenantController extends BaseController {
}
@PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/tenants", params = { "limit" }, method = RequestMethod.GET)
@RequestMapping(value = "/tenants", params = {"limit"}, method = RequestMethod.GET)
@ResponseBody
public TextPageData<Tenant> getTenants(@RequestParam int limit,
@RequestParam(required = false) String textSearch,
@ -85,5 +103,5 @@ public class TenantController extends BaseController {
throw handleException(e);
}
}
}

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

@ -23,9 +23,7 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.server.service.component.ComponentDiscoveryService;
import org.thingsboard.server.service.install.DatabaseSchemaService;
import org.thingsboard.server.service.install.DatabaseUpgradeService;
import org.thingsboard.server.service.install.SystemDataLoaderService;
import org.thingsboard.server.service.install.*;
import java.nio.file.Paths;
@ -40,9 +38,6 @@ public class ThingsboardInstallService {
@Value("${install.upgrade.from_version:1.2.3}")
private String upgradeFromVersion;
@Value("${install.data_dir}")
private String dataDir;
@Value("${install.load_demo:false}")
private Boolean loadDemo;
@ -61,6 +56,9 @@ public class ThingsboardInstallService {
@Autowired
private SystemDataLoaderService systemDataLoaderService;
@Autowired
private DataUpdateService dataUpdateService;
public void performInstall() {
try {
if (isUpgrade) {
@ -87,6 +85,8 @@ public class ThingsboardInstallService {
databaseUpgradeService.upgradeDatabase("1.4.0");
dataUpdateService.updateData("1.4.0");
log.info("Updating system data...");
systemDataLoaderService.deleteSystemWidgetBundle("charts");
@ -113,13 +113,6 @@ public class ThingsboardInstallService {
log.info("Starting ThingsBoard Installation...");
if (this.dataDir == null) {
throw new RuntimeException("'install.data_dir' property should specified!");
}
if (!Paths.get(this.dataDir).toFile().isDirectory()) {
throw new RuntimeException("'install.data_dir' property value is not a valid directory!");
}
log.info("Installing DataBase schema...");
databaseSchemaService.createDatabaseSchema();
@ -131,8 +124,8 @@ public class ThingsboardInstallService {
systemDataLoaderService.createSysAdmin();
systemDataLoaderService.createAdminSettings();
systemDataLoaderService.loadSystemWidgets();
systemDataLoaderService.loadSystemPlugins();
systemDataLoaderService.loadSystemRules();
// systemDataLoaderService.loadSystemPlugins();
// systemDataLoaderService.loadSystemRules();
if (loadDemo) {
log.info("Loading demo data...");

30
application/src/main/java/org/thingsboard/server/service/cluster/rpc/ClusterGrpcService.java

@ -19,7 +19,6 @@ import com.google.protobuf.ByteString;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@ -27,29 +26,24 @@ import org.springframework.util.SerializationUtils;
import org.thingsboard.server.actors.rpc.RpcBroadcastMsg;
import org.thingsboard.server.actors.rpc.RpcSessionCreateRequestMsg;
import org.thingsboard.server.actors.rpc.RpcSessionTellMsg;
import org.thingsboard.server.actors.service.ActorService;
import org.thingsboard.server.common.data.id.EntityId;
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.common.msg.device.DeviceToDeviceActorMsg;
import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse;
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequest;
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg;
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
import org.thingsboard.server.extensions.api.plugins.msg.ToPluginRpcResponseDeviceMsg;
import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg;
import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
import org.thingsboard.server.gen.cluster.ClusterRpcServiceGrpc;
import org.thingsboard.server.service.cluster.discovery.DiscoveryService;
import org.thingsboard.server.service.cluster.discovery.ServerInstance;
import org.thingsboard.server.service.cluster.discovery.ServerInstanceService;
import org.thingsboard.server.service.cluster.routing.ClusterRoutingService;
import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.IOException;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@ -124,7 +118,7 @@ public class ClusterGrpcService extends ClusterRpcServiceGrpc.ClusterRpcServiceI
}
@Override
public void tell(ServerAddress serverAddress, ToDeviceActorMsg toForward) {
public void tell(ServerAddress serverAddress, DeviceToDeviceActorMsg toForward) {
ClusterAPIProtos.ToRpcServerMessage msg = ClusterAPIProtos.ToRpcServerMessage.newBuilder()
.setToDeviceActorRpcMsg(toProtoMsg(toForward)).build();
tell(serverAddress, msg);
@ -138,7 +132,7 @@ public class ClusterGrpcService extends ClusterRpcServiceGrpc.ClusterRpcServiceI
}
@Override
public void tell(ServerAddress serverAddress, ToDeviceRpcRequestPluginMsg toForward) {
public void tell(ServerAddress serverAddress, ToDeviceRpcRequestActorMsg toForward) {
ClusterAPIProtos.ToRpcServerMessage msg = ClusterAPIProtos.ToRpcServerMessage.newBuilder()
.setToDeviceRpcRequestRpcMsg(toProtoMsg(toForward)).build();
tell(serverAddress, msg);
@ -190,7 +184,7 @@ public class ClusterGrpcService extends ClusterRpcServiceGrpc.ClusterRpcServiceI
}
}
private static ClusterAPIProtos.ToDeviceActorRpcMessage toProtoMsg(ToDeviceActorMsg msg) {
private static ClusterAPIProtos.ToDeviceActorRpcMessage toProtoMsg(DeviceToDeviceActorMsg msg) {
return ClusterAPIProtos.ToDeviceActorRpcMessage.newBuilder().setData(
ByteString.copyFrom(SerializationUtils.serialize(msg))
).build();
@ -202,15 +196,10 @@ public class ClusterGrpcService extends ClusterRpcServiceGrpc.ClusterRpcServiceI
).build();
}
private static ClusterAPIProtos.ToDeviceRpcRequestRpcMessage toProtoMsg(ToDeviceRpcRequestPluginMsg msg) {
private static ClusterAPIProtos.ToDeviceRpcRequestRpcMessage toProtoMsg(ToDeviceRpcRequestActorMsg msg) {
ClusterAPIProtos.ToDeviceRpcRequestRpcMessage.Builder builder = ClusterAPIProtos.ToDeviceRpcRequestRpcMessage.newBuilder();
ToDeviceRpcRequest request = msg.getMsg();
builder.setAddress(ClusterAPIProtos.PluginAddress.newBuilder()
.setTenantId(toUid(msg.getPluginTenantId().getId()))
.setPluginId(toUid(msg.getPluginId().getId()))
.build());
builder.setDeviceTenantId(toUid(msg.getTenantId()));
builder.setDeviceId(toUid(msg.getDeviceId()));
@ -227,11 +216,6 @@ public class ClusterGrpcService extends ClusterRpcServiceGrpc.ClusterRpcServiceI
ClusterAPIProtos.ToPluginRpcResponseRpcMessage.Builder builder = ClusterAPIProtos.ToPluginRpcResponseRpcMessage.newBuilder();
FromDeviceRpcResponse request = msg.getResponse();
builder.setAddress(ClusterAPIProtos.PluginAddress.newBuilder()
.setTenantId(toUid(msg.getPluginTenantId().getId()))
.setPluginId(toUid(msg.getPluginId().getId()))
.build());
builder.setMsgId(toUid(request.getId()));
request.getResponse().ifPresent(builder::setResponse);
request.getError().ifPresent(e -> builder.setError(e.name()));

9
application/src/main/java/org/thingsboard/server/service/cluster/rpc/ClusterRpcService.java

@ -19,12 +19,12 @@ import io.grpc.stub.StreamObserver;
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.common.msg.device.DeviceToDeviceActorMsg;
import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg;
import org.thingsboard.server.extensions.api.plugins.msg.ToPluginRpcResponseDeviceMsg;
import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg;
import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg;
import java.util.UUID;
@ -35,13 +35,13 @@ public interface ClusterRpcService {
void init(RpcMsgListener listener);
void tell(ServerAddress serverAddress, ToDeviceActorMsg toForward);
void tell(ServerAddress serverAddress, DeviceToDeviceActorMsg toForward);
void tell(ServerAddress serverAddress, ToDeviceSessionActorMsg toForward);
void tell(ServerAddress serverAddress, ToDeviceActorNotificationMsg toForward);
void tell(ServerAddress serverAddress, ToDeviceRpcRequestPluginMsg toForward);
void tell(ServerAddress serverAddress, ToDeviceRpcRequestActorMsg toForward);
void tell(ServerAddress serverAddress, ToPluginRpcResponseDeviceMsg toForward);
@ -50,4 +50,5 @@ public interface ClusterRpcService {
void broadcast(ToAllNodesMsg msg);
void onSessionCreated(UUID msgUid, StreamObserver<ClusterAPIProtos.ToRpcServerMessage> inputStream);
}

6
application/src/main/java/org/thingsboard/server/service/cluster/rpc/RpcMsgListener.java

@ -20,18 +20,16 @@ import org.thingsboard.server.actors.rpc.RpcSessionCreateRequestMsg;
import org.thingsboard.server.actors.rpc.RpcSessionTellMsg;
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.common.msg.device.DeviceToDeviceActorMsg;
import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
import org.thingsboard.server.extensions.api.plugins.msg.ToPluginActorMsg;
import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg;
import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
/**
* @author Andrew Shvayka
*/
public interface RpcMsgListener {
void onMsg(ToDeviceActorMsg msg);
void onMsg(DeviceToDeviceActorMsg msg);
void onMsg(ToDeviceActorNotificationMsg msg);

33
application/src/main/java/org/thingsboard/server/service/executors/ExternalCallExecutorService.java

@ -0,0 +1,33 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.executors;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class ExternalCallExecutorService extends AbstractListeningExecutor {
@Value("${actors.rule.external_call_thread_pool_size}")
private int externalCallExecutorThreadPoolSize;
@Override
protected int getThreadPollSize() {
return externalCallExecutorThreadPoolSize;
}
}

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

@ -37,17 +37,16 @@ public class CassandraDatabaseSchemaService implements DatabaseSchemaService {
private static final String CASSANDRA_DIR = "cassandra";
private static final String SCHEMA_CQL = "schema.cql";
@Value("${install.data_dir}")
private String dataDir;
@Autowired
private CassandraInstallCluster cluster;
@Autowired
private InstallScripts installScripts;
@Override
public void createDatabaseSchema() throws Exception {
log.info("Installing Cassandra DataBase schema...");
Path schemaFile = Paths.get(this.dataDir, CASSANDRA_DIR, SCHEMA_CQL);
Path schemaFile = Paths.get(installScripts.getDataDir(), CASSANDRA_DIR, SCHEMA_CQL);
loadCql(schemaFile);
}

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

@ -43,9 +43,6 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
private static final String SCHEMA_UPDATE_CQL = "schema_update.cql";
@Value("${install.data_dir}")
private String dataDir;
@Autowired
private CassandraCluster cluster;
@ -55,6 +52,9 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
@Autowired
private DashboardService dashboardService;
@Autowired
private InstallScripts installScripts;
@Override
public void upgradeDatabase(String fromVersion) throws Exception {
@ -91,7 +91,7 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
log.info("Relations dumped.");
log.info("Updating schema ...");
Path schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.3.0", SCHEMA_UPDATE_CQL);
Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "1.3.0", SCHEMA_UPDATE_CQL);
loadCql(schemaUpdateFile);
log.info("Schema updated.");
@ -173,7 +173,7 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
log.info("Updating schema ...");
schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.4.0", SCHEMA_UPDATE_CQL);
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "1.4.0", SCHEMA_UPDATE_CQL);
loadCql(schemaUpdateFile);
log.info("Schema updated.");
@ -189,7 +189,7 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
case "1.4.0":
log.info("Updating schema ...");
schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.5.0", SCHEMA_UPDATE_CQL);
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "1.5.0", SCHEMA_UPDATE_CQL);
loadCql(schemaUpdateFile);
log.info("Schema updated.");

22
application/src/main/java/org/thingsboard/server/service/install/DataUpdateService.java

@ -0,0 +1,22 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.install;
public interface DataUpdateService {
void updateData(String fromVersion) throws Exception;
}

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

@ -0,0 +1,106 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.install;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.IdBased;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.tenant.TenantService;
import java.util.List;
import java.util.UUID;
@Service
@Profile("install")
@Slf4j
public class DefaultDataUpdateService implements DataUpdateService {
@Autowired
private TenantService tenantService;
@Autowired
private RuleChainService ruleChainService;
@Autowired
private InstallScripts installScripts;
@Override
public void updateData(String fromVersion) throws Exception {
switch (fromVersion) {
case "1.4.0":
log.info("Updating data from version 1.4.0 to 1.5.0 ...");
tenantsDefaultRuleChainUpdater.updateEntities(null);
break;
default:
throw new RuntimeException("Unable to update data, unsupported fromVersion: " + fromVersion);
}
}
private PaginatedUpdater<String, Tenant> tenantsDefaultRuleChainUpdater =
new PaginatedUpdater<String, Tenant>() {
@Override
protected List<Tenant> findEntities(String region, TextPageLink pageLink) {
return tenantService.findTenants(pageLink).getData();
}
@Override
protected void updateEntity(Tenant tenant) {
try {
RuleChain ruleChain = ruleChainService.getRootTenantRuleChain(tenant.getId());
if (ruleChain == null) {
installScripts.createDefaultRuleChains(tenant.getId());
}
} catch (Exception e) {
log.error("Unable to update Tenant", e);
}
}
};
public abstract class PaginatedUpdater<I, D extends IdBased<?>> {
private static final int DEFAULT_LIMIT = 100;
public void updateEntities(I id) {
TextPageLink pageLink = new TextPageLink(DEFAULT_LIMIT);
boolean hasNext = true;
while (hasNext) {
List<D> entities = findEntities(id, pageLink);
for (D entity : entities) {
updateEntity(entity);
}
hasNext = entities.size() == pageLink.getLimit();
if (hasNext) {
int index = entities.size() - 1;
UUID idOffset = entities.get(index).getUuidId();
pageLink.setIdOffset(idOffset);
}
}
}
protected abstract List<D> findEntities(I id, TextPageLink pageLink);
protected abstract void updateEntity(D entity);
}
}

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

@ -60,21 +60,12 @@ import java.nio.file.Paths;
@Slf4j
public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
private static final String JSON_DIR = "json";
private static final String SYSTEM_DIR = "system";
private static final String DEMO_DIR = "demo";
private static final String WIDGET_BUNDLES_DIR = "widget_bundles";
private static final String PLUGINS_DIR = "plugins";
private static final String RULES_DIR = "rules";
private static final String DASHBOARDS_DIR = "dashboards";
private static final ObjectMapper objectMapper = new ObjectMapper();
public static final String JSON_EXT = ".json";
public static final String CUSTOMER_CRED = "customer";
public static final String DEFAULT_DEVICE_TYPE = "default";
@Value("${install.data_dir}")
private String dataDir;
@Autowired
private InstallScripts installScripts;
@Autowired
private BCryptPasswordEncoder passwordEncoder;
@ -88,15 +79,6 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
@Autowired
private WidgetsBundleService widgetsBundleService;
@Autowired
private WidgetTypeService widgetTypeService;
@Autowired
private PluginService pluginService;
@Autowired
private RuleService ruleService;
@Autowired
private TenantService tenantService;
@ -109,9 +91,6 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
@Autowired
private DeviceCredentialsService deviceCredentialsService;
@Autowired
private DashboardService dashboardService;
@Bean
protected BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
@ -146,56 +125,13 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
adminSettingsService.saveAdminSettings(mailSettings);
}
@Override
public void loadSystemWidgets() throws Exception {
Path widgetBundlesDir = Paths.get(dataDir, JSON_DIR, SYSTEM_DIR, WIDGET_BUNDLES_DIR);
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(widgetBundlesDir, path -> path.toString().endsWith(JSON_EXT))) {
dirStream.forEach(
path -> {
try {
JsonNode widgetsBundleDescriptorJson = objectMapper.readTree(path.toFile());
JsonNode widgetsBundleJson = widgetsBundleDescriptorJson.get("widgetsBundle");
WidgetsBundle widgetsBundle = objectMapper.treeToValue(widgetsBundleJson, WidgetsBundle.class);
WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle);
JsonNode widgetTypesArrayJson = widgetsBundleDescriptorJson.get("widgetTypes");
widgetTypesArrayJson.forEach(
widgetTypeJson -> {
try {
WidgetType widgetType = objectMapper.treeToValue(widgetTypeJson, WidgetType.class);
widgetType.setBundleAlias(savedWidgetsBundle.getAlias());
widgetTypeService.saveWidgetType(widgetType);
} catch (Exception e) {
log.error("Unable to load widget type from json: [{}]", path.toString());
throw new RuntimeException("Unable to load widget type from json", e);
}
}
);
} catch (Exception e) {
log.error("Unable to load widgets bundle from json: [{}]", path.toString());
throw new RuntimeException("Unable to load widgets bundle from json", e);
}
}
);
}
}
@Override
public void loadSystemPlugins() throws Exception {
loadPlugins(Paths.get(dataDir, JSON_DIR, SYSTEM_DIR, PLUGINS_DIR), null);
}
@Override
public void loadSystemRules() throws Exception {
// loadRules(Paths.get(dataDir, JSON_DIR, SYSTEM_DIR, RULES_DIR), null);
}
@Override
public void loadDemoData() throws Exception {
Tenant demoTenant = new Tenant();
demoTenant.setRegion("Global");
demoTenant.setTitle("Tenant");
demoTenant = tenantService.saveTenant(demoTenant);
installScripts.createDefaultRuleChains(demoTenant.getId());
createUser(Authority.TENANT_ADMIN, demoTenant.getId(), null, "tenant@thingsboard.org", "tenant");
Customer customerA = new Customer();
@ -227,9 +163,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
createDevice(demoTenant.getId(), null, DEFAULT_DEVICE_TYPE, "Raspberry Pi Demo Device", "RASPBERRY_PI_DEMO_TOKEN", "Demo device that is used in " +
"Raspberry Pi GPIO control sample application");
loadPlugins(Paths.get(dataDir, JSON_DIR, DEMO_DIR, PLUGINS_DIR), demoTenant.getId());
// loadRules(Paths.get(dataDir, JSON_DIR, DEMO_DIR, RULES_DIR), demoTenant.getId());
loadDashboards(Paths.get(dataDir, JSON_DIR, DEMO_DIR, DASHBOARDS_DIR), demoTenant.getId(), null);
installScripts.loadDashboards(demoTenant.getId(), null);
}
@Override
@ -240,6 +174,11 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
}
}
@Override
public void loadSystemWidgets() throws Exception {
installScripts.loadSystemWidgets();
}
private User createUser(Authority authority,
TenantId tenantId,
CustomerId customerId,
@ -282,72 +221,4 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
return device;
}
private void loadPlugins(Path pluginsDir, TenantId tenantId) throws Exception{
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(pluginsDir, path -> path.toString().endsWith(JSON_EXT))) {
dirStream.forEach(
path -> {
try {
JsonNode pluginJson = objectMapper.readTree(path.toFile());
PluginMetaData plugin = objectMapper.treeToValue(pluginJson, PluginMetaData.class);
plugin.setTenantId(tenantId);
if (plugin.getState() == ComponentLifecycleState.ACTIVE) {
plugin.setState(ComponentLifecycleState.SUSPENDED);
PluginMetaData savedPlugin = pluginService.savePlugin(plugin);
pluginService.activatePluginById(savedPlugin.getId());
} else {
pluginService.savePlugin(plugin);
}
} catch (Exception e) {
log.error("Unable to load plugin from json: [{}]", path.toString());
throw new RuntimeException("Unable to load plugin from json", e);
}
}
);
}
}
private void loadRules(Path rulesDir, TenantId tenantId) throws Exception {
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(rulesDir, path -> path.toString().endsWith(JSON_EXT))) {
dirStream.forEach(
path -> {
try {
JsonNode ruleJson = objectMapper.readTree(path.toFile());
RuleMetaData rule = objectMapper.treeToValue(ruleJson, RuleMetaData.class);
rule.setTenantId(tenantId);
if (rule.getState() == ComponentLifecycleState.ACTIVE) {
rule.setState(ComponentLifecycleState.SUSPENDED);
RuleMetaData savedRule = ruleService.saveRule(rule);
ruleService.activateRuleById(savedRule.getId());
} else {
ruleService.saveRule(rule);
}
} catch (Exception e) {
log.error("Unable to load rule from json: [{}]", path.toString());
throw new RuntimeException("Unable to load rule from json", e);
}
}
);
}
}
private void loadDashboards(Path dashboardsDir, TenantId tenantId, CustomerId customerId) throws Exception {
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(dashboardsDir, path -> path.toString().endsWith(JSON_EXT))) {
dirStream.forEach(
path -> {
try {
JsonNode dashboardJson = objectMapper.readTree(path.toFile());
Dashboard dashboard = objectMapper.treeToValue(dashboardJson, Dashboard.class);
dashboard.setTenantId(tenantId);
Dashboard savedDashboard = dashboardService.saveDashboard(dashboard);
if (customerId != null && !customerId.isNullUid()) {
dashboardService.assignDashboardToCustomer(savedDashboard.getId(), customerId);
}
} catch (Exception e) {
log.error("Unable to load dashboard from json: [{}]", path.toString());
throw new RuntimeException("Unable to load dashboard from json", e);
}
}
);
}
}
}

185
application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java

@ -0,0 +1,185 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.install;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.widget.WidgetType;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.widget.WidgetTypeService;
import org.thingsboard.server.dao.widget.WidgetsBundleService;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import static org.thingsboard.server.service.install.DatabaseHelper.objectMapper;
/**
* Created by ashvayka on 18.04.18.
*/
@Component
@Slf4j
public class InstallScripts {
public static final String APP_DIR = "application";
public static final String SRC_DIR = "src";
public static final String MAIN_DIR = "main";
public static final String DATA_DIR = "data";
public static final String JSON_DIR = "json";
public static final String SYSTEM_DIR = "system";
public static final String TENANT_DIR = "tenant";
public static final String DEMO_DIR = "demo";
public static final String RULE_CHAINS_DIR = "rule_chains";
public static final String WIDGET_BUNDLES_DIR = "widget_bundles";
public static final String DASHBOARDS_DIR = "dashboards";
public static final String JSON_EXT = ".json";
@Value("${install.data_dir:}")
private String dataDir;
@Autowired
private RuleChainService ruleChainService;
@Autowired
private DashboardService dashboardService;
@Autowired
private WidgetTypeService widgetTypeService;
@Autowired
private WidgetsBundleService widgetsBundleService;
public Path getTenantRuleChainsDir() {
return Paths.get(getDataDir(), JSON_DIR, TENANT_DIR, RULE_CHAINS_DIR);
}
public String getDataDir() {
if (!StringUtils.isEmpty(dataDir)) {
if (!Paths.get(this.dataDir).toFile().isDirectory()) {
throw new RuntimeException("'install.data_dir' property value is not a valid directory!");
}
return dataDir;
} else {
String workDir = System.getProperty("user.dir");
if (workDir.endsWith("application")) {
return Paths.get(workDir, SRC_DIR, MAIN_DIR, DATA_DIR).toString();
} else {
Path dataDirPath = Paths.get(workDir, APP_DIR, SRC_DIR, MAIN_DIR, DATA_DIR);
if (Files.exists(dataDirPath)) {
return dataDirPath.toString();
} else {
throw new RuntimeException("Not valid working directory: " + workDir + ". Please use either root project directory, application module directory or specify valid \"install.data_dir\" ENV variable to avoid automatic data directory lookup!");
}
}
}
}
public void createDefaultRuleChains(TenantId tenantId) throws IOException {
Path tenantChainsDir = getTenantRuleChainsDir();
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(tenantChainsDir, path -> path.toString().endsWith(InstallScripts.JSON_EXT))) {
dirStream.forEach(
path -> {
try {
JsonNode ruleChainJson = objectMapper.readTree(path.toFile());
RuleChain ruleChain = objectMapper.treeToValue(ruleChainJson.get("ruleChain"), RuleChain.class);
RuleChainMetaData ruleChainMetaData = objectMapper.treeToValue(ruleChainJson.get("metadata"), RuleChainMetaData.class);
ruleChain.setTenantId(tenantId);
ruleChain = ruleChainService.saveRuleChain(ruleChain);
ruleChainMetaData.setRuleChainId(ruleChain.getId());
ruleChainService.saveRuleChainMetaData(ruleChainMetaData);
} catch (Exception e) {
log.error("Unable to load rule chain from json: [{}]", path.toString());
throw new RuntimeException("Unable to load rule chain from json", e);
}
}
);
}
}
public void loadSystemWidgets() throws Exception {
Path widgetBundlesDir = Paths.get(getDataDir(), JSON_DIR, SYSTEM_DIR, WIDGET_BUNDLES_DIR);
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(widgetBundlesDir, path -> path.toString().endsWith(JSON_EXT))) {
dirStream.forEach(
path -> {
try {
JsonNode widgetsBundleDescriptorJson = objectMapper.readTree(path.toFile());
JsonNode widgetsBundleJson = widgetsBundleDescriptorJson.get("widgetsBundle");
WidgetsBundle widgetsBundle = objectMapper.treeToValue(widgetsBundleJson, WidgetsBundle.class);
WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle);
JsonNode widgetTypesArrayJson = widgetsBundleDescriptorJson.get("widgetTypes");
widgetTypesArrayJson.forEach(
widgetTypeJson -> {
try {
WidgetType widgetType = objectMapper.treeToValue(widgetTypeJson, WidgetType.class);
widgetType.setBundleAlias(savedWidgetsBundle.getAlias());
widgetTypeService.saveWidgetType(widgetType);
} catch (Exception e) {
log.error("Unable to load widget type from json: [{}]", path.toString());
throw new RuntimeException("Unable to load widget type from json", e);
}
}
);
} catch (Exception e) {
log.error("Unable to load widgets bundle from json: [{}]", path.toString());
throw new RuntimeException("Unable to load widgets bundle from json", e);
}
}
);
}
}
public void loadDashboards(TenantId tenantId, CustomerId customerId) throws Exception {
Path dashboardsDir = Paths.get(getDataDir(), JSON_DIR, DEMO_DIR, DASHBOARDS_DIR);
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(dashboardsDir, path -> path.toString().endsWith(JSON_EXT))) {
dirStream.forEach(
path -> {
try {
JsonNode dashboardJson = objectMapper.readTree(path.toFile());
Dashboard dashboard = objectMapper.treeToValue(dashboardJson, Dashboard.class);
dashboard.setTenantId(tenantId);
Dashboard savedDashboard = dashboardService.saveDashboard(dashboard);
if (customerId != null && !customerId.isNullUid()) {
dashboardService.assignDashboardToCustomer(savedDashboard.getId(), customerId);
}
} catch (Exception e) {
log.error("Unable to load dashboard from json: [{}]", path.toString());
throw new RuntimeException("Unable to load dashboard from json", e);
}
}
);
}
}
}

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

@ -16,6 +16,7 @@
package org.thingsboard.server.service.install;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
@ -38,9 +39,6 @@ public class SqlDatabaseSchemaService implements DatabaseSchemaService {
private static final String SQL_DIR = "sql";
private static final String SCHEMA_SQL = "schema.sql";
@Value("${install.data_dir}")
private String dataDir;
@Value("${spring.datasource.url}")
private String dbUrl;
@ -50,12 +48,15 @@ public class SqlDatabaseSchemaService implements DatabaseSchemaService {
@Value("${spring.datasource.password}")
private String dbPassword;
@Autowired
private InstallScripts installScripts;
@Override
public void createDatabaseSchema() throws Exception {
log.info("Installing SQL DataBase schema...");
Path schemaFile = Paths.get(this.dataDir, SQL_DIR, SCHEMA_SQL);
Path schemaFile = Paths.get(installScripts.getDataDir(), SQL_DIR, SCHEMA_SQL);
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
String sql = new String(Files.readAllBytes(schemaFile), Charset.forName("UTF-8"));
conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to load initial thingsboard database schema

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

@ -44,9 +44,6 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
private static final String SCHEMA_UPDATE_SQL = "schema_update.sql";
@Value("${install.data_dir}")
private String dataDir;
@Value("${spring.datasource.url}")
private String dbUrl;
@ -59,12 +56,15 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
@Autowired
private DashboardService dashboardService;
@Autowired
private InstallScripts installScripts;
@Override
public void upgradeDatabase(String fromVersion) throws Exception {
switch (fromVersion) {
case "1.3.0":
log.info("Updating schema ...");
Path schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.3.1", SCHEMA_UPDATE_SQL);
Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "1.3.1", SCHEMA_UPDATE_SQL);
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
String sql = new String(Files.readAllBytes(schemaUpdateFile), Charset.forName("UTF-8"));
conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
@ -82,7 +82,7 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
log.info("Dashboards dumped.");
log.info("Updating schema ...");
schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.4.0", SCHEMA_UPDATE_SQL);
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "1.4.0", SCHEMA_UPDATE_SQL);
String sql = new String(Files.readAllBytes(schemaUpdateFile), Charset.forName("UTF-8"));
conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
log.info("Schema updated.");
@ -100,7 +100,7 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
case "1.4.0":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Updating schema ...");
schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.5.0", SCHEMA_UPDATE_SQL);
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "1.5.0", SCHEMA_UPDATE_SQL);
String sql = new String(Files.readAllBytes(schemaUpdateFile), Charset.forName("UTF-8"));
conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
log.info("Schema updated.");

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

@ -23,10 +23,6 @@ public interface SystemDataLoaderService {
void loadSystemWidgets() throws Exception;
void loadSystemPlugins() throws Exception;
void loadSystemRules() throws Exception;
void loadDemoData() throws Exception;
void deleteSystemWidgetBundle(String bundleAlias) throws Exception;

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

@ -0,0 +1,157 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.rpc;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.server.actors.service.ActorService;
import org.thingsboard.server.common.data.audit.ActionType;
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.data.id.UUIDBased;
import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.core.ToServerRpcResponseMsg;
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
import org.thingsboard.server.controller.BaseController;
import org.thingsboard.server.dao.audit.AuditLogService;
import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse;
import org.thingsboard.server.extensions.api.plugins.msg.RpcError;
import org.thingsboard.server.service.cluster.routing.ClusterRoutingService;
import org.thingsboard.server.service.cluster.rpc.ClusterRpcService;
import org.thingsboard.server.service.security.model.SecurityUser;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.IOException;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
/**
* Created by ashvayka on 27.03.18.
*/
@Service
@Slf4j
public class DefaultDeviceRpcService implements DeviceRpcService {
@Autowired
private ClusterRoutingService routingService;
@Autowired
private ClusterRpcService rpcService;
@Autowired
private ActorService actorService;
@Autowired
private AuditLogService auditLogService;
private ScheduledExecutorService rpcCallBackExecutor;
private final ConcurrentMap<UUID, Consumer<FromDeviceRpcResponse>> localRpcRequests = new ConcurrentHashMap<>();
@PostConstruct
public void initExecutor() {
rpcCallBackExecutor = Executors.newSingleThreadScheduledExecutor();
}
@PreDestroy
public void shutdownExecutor() {
if (rpcCallBackExecutor != null) {
rpcCallBackExecutor.shutdownNow();
}
}
@Override
public void process(ToDeviceRpcRequest request, Consumer<FromDeviceRpcResponse> responseConsumer) {
log.trace("[{}] Processing local rpc call for device [{}]", request.getTenantId(), request.getDeviceId());
sendRpcRequest(request);
UUID requestId = request.getId();
localRpcRequests.put(requestId, responseConsumer);
long timeout = Math.max(0, request.getExpirationTime() - System.currentTimeMillis());
log.error("[{}] processing the request: [{}]", this.hashCode(), requestId);
rpcCallBackExecutor.schedule(() -> {
log.error("[{}] timeout the request: [{}]", this.hashCode(), requestId);
Consumer<FromDeviceRpcResponse> consumer = localRpcRequests.remove(requestId);
if (consumer != null) {
consumer.accept(new FromDeviceRpcResponse(requestId, null, RpcError.TIMEOUT));
}
}, timeout, TimeUnit.MILLISECONDS);
}
@Override
public void process(ToDeviceRpcRequest request, ServerAddress originator) {
// 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());
// }
}
@Override
public void process(FromDeviceRpcResponse response) {
log.error("[{}] response the request: [{}]", this.hashCode(), response.getId());
//TODO: send to another server if needed.
UUID requestId = response.getId();
Consumer<FromDeviceRpcResponse> consumer = localRpcRequests.remove(requestId);
if (consumer != null) {
consumer.accept(response);
} else {
log.trace("[{}] Unknown or stale rpc response received [{}]", requestId, response);
}
}
@Override
public void sendRpcReplyToDevice(TenantId tenantId, DeviceId deviceId, int requestId, String body) {
ToServerRpcResponseActorMsg rpcMsg = new ToServerRpcResponseActorMsg(tenantId, deviceId, new ToServerRpcResponseMsg(requestId, body));
forward(deviceId, rpcMsg, rpcService::tell);
}
private void sendRpcRequest(ToDeviceRpcRequest msg) {
log.trace("[{}] Forwarding msg {} to device actor!", msg.getDeviceId(), msg);
ToDeviceRpcRequestActorMsg rpcMsg = new ToDeviceRpcRequestActorMsg(msg);
forward(msg.getDeviceId(), rpcMsg, rpcService::tell);
}
private <T extends ToDeviceActorNotificationMsg> 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!", msg.getTenantId(), msg);
rpcFunction.accept(instance.get(), msg);
} else {
log.trace("[{}] Forwarding msg {} to local device actor!", msg.getTenantId(), msg);
actorService.onMsg(msg);
}
}
}

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

@ -0,0 +1,39 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.rpc;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse;
import java.util.function.Consumer;
/**
* Created by ashvayka on 16.04.18.
*/
public interface DeviceRpcService {
void process(ToDeviceRpcRequest request, Consumer<FromDeviceRpcResponse> responseConsumer);
void process(ToDeviceRpcRequest request, ServerAddress originator);
void process(FromDeviceRpcResponse response);
void sendRpcReplyToDevice(TenantId tenantId, DeviceId deviceId, int requestId, String body);
}

8
extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/LocalRequestMetaData.java → application/src/main/java/org/thingsboard/server/service/rpc/LocalRequestMetaData.java

@ -13,18 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.extensions.core.plugin.rpc;
package org.thingsboard.server.service.rpc;
import lombok.Data;
import org.springframework.http.ResponseEntity;
import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequest;
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
import org.thingsboard.server.service.security.model.SecurityUser;
/**
* @author Andrew Shvayka
* Created by ashvayka on 16.04.18.
*/
@Data
public class LocalRequestMetaData {
private final ToDeviceRpcRequest request;
private final SecurityUser user;
private final DeferredResult<ResponseEntity> responseWriter;
}

23
extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/ToDeviceRpcRequestPluginMsg.java → application/src/main/java/org/thingsboard/server/service/rpc/ToDeviceRpcRequestActorMsg.java

@ -13,36 +13,33 @@
* 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.service.rpc;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.PluginId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
import java.util.Optional;
/**
* @author Andrew Shvayka
* Created by ashvayka on 16.04.18.
*/
@ToString
@RequiredArgsConstructor
public class ToDeviceRpcRequestPluginMsg implements ToDeviceActorNotificationMsg {
public class ToDeviceRpcRequestActorMsg implements ToDeviceActorNotificationMsg {
private final ServerAddress serverAddress;
@Getter
private final PluginId pluginId;
@Getter
private final TenantId pluginTenantId;
@Getter
private final ToDeviceRpcRequest msg;
public ToDeviceRpcRequestPluginMsg(PluginId pluginId, TenantId pluginTenantId, ToDeviceRpcRequest msg) {
this(null, pluginId, pluginTenantId, msg);
public ToDeviceRpcRequestActorMsg(ToDeviceRpcRequest msg) {
this(null, msg);
}
public Optional<ServerAddress> getServerAddress() {
@ -58,5 +55,9 @@ public class ToDeviceRpcRequestPluginMsg implements ToDeviceActorNotificationMsg
public TenantId getTenantId() {
return msg.getTenantId();
}
}
@Override
public MsgType getMsgType() {
return MsgType.DEVICE_RPC_REQUEST_TO_DEVICE_ACTOR_MSG;
}
}

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

@ -0,0 +1,60 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.rpc;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.core.ToServerRpcResponseMsg;
import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
import java.util.Optional;
/**
* Created by ashvayka on 16.04.18.
*/
@ToString
@RequiredArgsConstructor
public class ToServerRpcResponseActorMsg implements ToDeviceActorNotificationMsg {
private final ServerAddress serverAddress;
@Getter
private final TenantId tenantId;
@Getter
private final DeviceId deviceId;
@Getter
private final ToServerRpcResponseMsg msg;
public ToServerRpcResponseActorMsg(TenantId tenantId, DeviceId deviceId, ToServerRpcResponseMsg msg) {
this(null, tenantId, deviceId, msg);
}
public Optional<ServerAddress> getServerAddress() {
return Optional.ofNullable(serverAddress);
}
@Override
public MsgType getMsgType() {
return MsgType.SERVER_RPC_RESPONSE_TO_DEVICE_ACTOR_MSG;
}
}

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

@ -118,7 +118,7 @@ public class NashornJsEngine implements org.thingsboard.rule.engine.api.ScriptEn
String newData = data != null ? data : msg.getData();
TbMsgMetaData newMetadata = metadata != null ? new TbMsgMetaData(metadata) : msg.getMetaData();
String newMessageType = !StringUtils.isEmpty(messageType) ? messageType : msg.getType();
return new TbMsg(msg.getId(), newMessageType, msg.getOriginator(), newMetadata, newData);
return new TbMsg(msg.getId(), newMessageType, msg.getOriginator(), newMetadata, newData, msg.getRuleChainId(), msg.getRuleNodeId(), msg.getClusterPartition());
} catch (Throwable th) {
th.printStackTrace();
throw new RuntimeException("Failed to unbind message data from javascript result", th);

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

@ -25,6 +25,7 @@ import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.server.actors.plugin.ValidationResult;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.Tenant;
@ -35,8 +36,10 @@ import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.controller.HttpValidationCallback;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.asset.AssetService;
@ -140,7 +143,7 @@ public class AccessValidator {
return response;
}
public <T> void validate(SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
public void validate(SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
switch (entityId.getEntityType()) {
case DEVICE:
validateDevice(currentUser, entityId, callback);
@ -177,14 +180,14 @@ public class AccessValidator {
} else if (currentUser.isCustomerUser() && !device.getCustomerId().equals(currentUser.getCustomerId())) {
return ValidationResult.accessDenied("Device doesn't belong to the current Customer!");
} else {
return ValidationResult.ok();
return ValidationResult.ok(device);
}
}
}), executor);
}
}
private <T> void validateAsset(final SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
private void validateAsset(final SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
if (currentUser.isSystemAdmin()) {
callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
} else {
@ -198,15 +201,14 @@ public class AccessValidator {
} else if (currentUser.isCustomerUser() && !asset.getCustomerId().equals(currentUser.getCustomerId())) {
return ValidationResult.accessDenied("Asset doesn't belong to the current Customer!");
} else {
return ValidationResult.ok();
return ValidationResult.ok(asset);
}
}
}), executor);
}
}
private <T> void validateRuleChain(final SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
private void validateRuleChain(final SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
if (currentUser.isCustomerUser()) {
callback.onSuccess(ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
} else {
@ -220,14 +222,40 @@ public class AccessValidator {
} else if (currentUser.isSystemAdmin() && !ruleChain.getTenantId().isNullUid()) {
return ValidationResult.accessDenied("Rule chain is not in system scope!");
} else {
return ValidationResult.ok();
return ValidationResult.ok(ruleChain);
}
}
}), executor);
}
}
private void validateRule(final SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
if (currentUser.isCustomerUser()) {
callback.onSuccess(ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
} else {
ListenableFuture<RuleNode> ruleNodeFuture = ruleChainService.findRuleNodeByIdAsync(new RuleNodeId(entityId.getId()));
Futures.addCallback(ruleNodeFuture, getCallback(callback, ruleNodeTmp -> {
RuleNode ruleNode = ruleNodeTmp;
if (ruleNode == null) {
return ValidationResult.entityNotFound("Rule node with requested id wasn't found!");
} else if (ruleNode.getRuleChainId() == null) {
return ValidationResult.entityNotFound("Rule chain with requested node id wasn't found!");
} else {
//TODO: make async
RuleChain ruleChain = ruleChainService.findRuleChainById(ruleNode.getRuleChainId());
if (currentUser.isTenantAdmin() && !ruleChain.getTenantId().equals(currentUser.getTenantId())) {
return ValidationResult.accessDenied("Rule chain doesn't belong to the current Tenant!");
} else if (currentUser.isSystemAdmin() && !ruleChain.getTenantId().isNullUid()) {
return ValidationResult.accessDenied("Rule chain is not in system scope!");
} else {
return ValidationResult.ok(ruleNode);
}
}
}), executor);
}
}
private <T> void validateCustomer(final SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
private void validateCustomer(final SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
if (currentUser.isSystemAdmin()) {
callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
} else {
@ -241,18 +269,18 @@ public class AccessValidator {
} else if (currentUser.isCustomerUser() && !customer.getId().equals(currentUser.getCustomerId())) {
return ValidationResult.accessDenied("Customer doesn't relate to the currently authorized customer user!");
} else {
return ValidationResult.ok();
return ValidationResult.ok(customer);
}
}
}), executor);
}
}
private <T> void validateTenant(final SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
private void validateTenant(final SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
if (currentUser.isCustomerUser()) {
callback.onSuccess(ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
} else if (currentUser.isSystemAdmin()) {
callback.onSuccess(ValidationResult.ok());
callback.onSuccess(ValidationResult.ok(null));
} else {
ListenableFuture<Tenant> tenantFuture = tenantService.findTenantByIdAsync(new TenantId(entityId.getId()));
Futures.addCallback(tenantFuture, getCallback(callback, tenant -> {
@ -261,13 +289,13 @@ public class AccessValidator {
} else if (!tenant.getId().equals(currentUser.getTenantId())) {
return ValidationResult.accessDenied("Tenant doesn't relate to the currently authorized user!");
} else {
return ValidationResult.ok();
return ValidationResult.ok(tenant);
}
}), executor);
}
}
private <T> FutureCallback<T> getCallback(FutureCallback<ValidationResult> callback, Function<T, ValidationResult> transformer) {
private <T, V> FutureCallback<T> getCallback(FutureCallback<ValidationResult> callback, Function<T, ValidationResult<V>> transformer) {
return new FutureCallback<T>() {
@Override
public void onSuccess(@Nullable T result) {

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

@ -0,0 +1,390 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.state;
import com.datastax.driver.core.utils.UUIDs;
import com.fasterxml.jackson.databind.ObjectMapper;
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 com.google.common.util.concurrent.ListeningScheduledExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.thingsboard.server.actors.service.ActorService;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.page.TextPageLink;
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.attributes.AttributesService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import static org.thingsboard.server.common.data.DataConstants.ACTIVITY_EVENT;
import static org.thingsboard.server.common.data.DataConstants.CONNECT_EVENT;
import static org.thingsboard.server.common.data.DataConstants.DISCONNECT_EVENT;
import static org.thingsboard.server.common.data.DataConstants.INACTIVITY_EVENT;
/**
* Created by ashvayka on 01.05.18.
*/
@Service
@Slf4j
//TODO: refactor to use page links as cursor and not fetch all
public class DefaultDeviceStateService implements DeviceStateService {
private static final ObjectMapper json = new ObjectMapper();
public static final String ACTIVITY_STATE = "active";
public static final String LAST_CONNECT_TIME = "lastConnectTime";
public static final String LAST_DISCONNECT_TIME = "lastDisconnectTime";
public static final String LAST_ACTIVITY_TIME = "lastActivityTime";
public static final String INACTIVITY_ALARM_TIME = "inactivityAlarmTime";
public static final String INACTIVITY_TIMEOUT = "inactivityTimeout";
public static final List<String> PERSISTENT_ATTRIBUTES = Arrays.asList(ACTIVITY_STATE, LAST_CONNECT_TIME, LAST_DISCONNECT_TIME, LAST_ACTIVITY_TIME, INACTIVITY_ALARM_TIME, INACTIVITY_TIMEOUT);
@Autowired
private TenantService tenantService;
@Autowired
private DeviceService deviceService;
@Autowired
private AttributesService attributesService;
@Autowired
private ActorService actorService;
@Autowired
private TelemetrySubscriptionService tsSubService;
@Value("${state.defaultInactivityTimeoutInSec}")
@Getter
private long defaultInactivityTimeoutInSec;
@Value("${state.defaultStateCheckIntervalInSec}")
@Getter
private long defaultStateCheckIntervalInSec;
// TODO in v2.1
// @Value("${state.defaultStatePersistenceIntervalInSec}")
// @Getter
// private long defaultStatePersistenceIntervalInSec;
//
// @Value("${state.defaultStatePersistencePack}")
// @Getter
// private long defaultStatePersistencePack;
private ListeningScheduledExecutorService queueExecutor;
private ConcurrentMap<TenantId, Set<DeviceId>> tenantDevices = new ConcurrentHashMap<>();
private ConcurrentMap<DeviceId, DeviceStateData> deviceStates = new ConcurrentHashMap<>();
@PostConstruct
public void init() {
// Should be always single threaded due to absence of locks.
queueExecutor = MoreExecutors.listeningDecorator(Executors.newSingleThreadScheduledExecutor());
queueExecutor.submit(this::initStateFromDB);
queueExecutor.scheduleAtFixedRate(this::updateState, defaultStateCheckIntervalInSec, defaultStateCheckIntervalInSec, TimeUnit.SECONDS);
//TODO: schedule persistence in v2.1;
}
@PreDestroy
public void stop() {
if (queueExecutor != null) {
queueExecutor.shutdownNow();
}
}
@Override
public void onDeviceAdded(Device device) {
queueExecutor.submit(() -> onDeviceAddedSync(device));
}
@Override
public void onDeviceUpdated(Device device) {
queueExecutor.submit(() -> onDeviceUpdatedSync(device));
}
@Override
public void onDeviceConnect(DeviceId deviceId) {
queueExecutor.submit(() -> onDeviceConnectSync(deviceId));
}
@Override
public void onDeviceActivity(DeviceId deviceId) {
queueExecutor.submit(() -> onDeviceActivitySync(deviceId));
}
@Override
public void onDeviceDisconnect(DeviceId deviceId) {
queueExecutor.submit(() -> onDeviceDisconnectSync(deviceId));
}
@Override
public void onDeviceDeleted(Device device) {
queueExecutor.submit(() -> onDeviceDeleted(device.getTenantId(), device.getId()));
}
@Override
public void onDeviceInactivityTimeoutUpdate(DeviceId deviceId, long inactivityTimeout) {
queueExecutor.submit(() -> onInactivityTimeoutUpdate(deviceId, inactivityTimeout));
}
@Override
public Optional<DeviceState> getDeviceState(DeviceId deviceId) {
DeviceStateData state = deviceStates.get(deviceId);
if (state != null) {
return Optional.of(state.getState());
} else {
return Optional.empty();
}
}
private void initStateFromDB() {
List<Tenant> tenants = tenantService.findTenants(new TextPageLink(Integer.MAX_VALUE)).getData();
for (Tenant tenant : tenants) {
List<ListenableFuture<DeviceStateData>> fetchFutures = new ArrayList<>();
List<Device> devices = deviceService.findDevicesByTenantId(tenant.getId(), new TextPageLink(Integer.MAX_VALUE)).getData();
for (Device device : devices) {
fetchFutures.add(fetchDeviceState(device));
}
try {
Futures.successfulAsList(fetchFutures).get().forEach(this::addDeviceUsingState);
} catch (InterruptedException | ExecutionException e) {
log.warn("Failed to init device state service from DB", e);
}
}
}
private void addDeviceUsingState(DeviceStateData state) {
tenantDevices.computeIfAbsent(state.getTenantId(), id -> ConcurrentHashMap.newKeySet()).add(state.getDeviceId());
deviceStates.put(state.getDeviceId(), state);
}
private void updateState() {
long ts = System.currentTimeMillis();
Set<DeviceId> deviceIds = new HashSet<>(deviceStates.keySet());
for (DeviceId deviceId : deviceIds) {
DeviceStateData stateData = deviceStates.get(deviceId);
DeviceState state = stateData.getState();
state.setActive(ts < state.getLastActivityTime() + state.getInactivityTimeout());
if (!state.isActive() && state.getLastInactivityAlarmTime() < state.getLastActivityTime()) {
state.setLastInactivityAlarmTime(ts);
pushRuleEngineMessage(stateData, INACTIVITY_EVENT);
saveAttribute(deviceId, INACTIVITY_ALARM_TIME, ts);
saveAttribute(deviceId, ACTIVITY_STATE, state.isActive());
}
}
}
private void onDeviceConnectSync(DeviceId deviceId) {
DeviceStateData stateData = deviceStates.get(deviceId);
if (stateData != null) {
long ts = System.currentTimeMillis();
stateData.getState().setLastConnectTime(ts);
pushRuleEngineMessage(stateData, CONNECT_EVENT);
saveAttribute(deviceId, LAST_CONNECT_TIME, ts);
}
}
private void onDeviceDisconnectSync(DeviceId deviceId) {
DeviceStateData stateData = deviceStates.get(deviceId);
if (stateData != null) {
long ts = System.currentTimeMillis();
stateData.getState().setLastDisconnectTime(ts);
pushRuleEngineMessage(stateData, DISCONNECT_EVENT);
saveAttribute(deviceId, LAST_DISCONNECT_TIME, ts);
}
}
private void onDeviceActivitySync(DeviceId deviceId) {
DeviceStateData stateData = deviceStates.get(deviceId);
if (stateData != null) {
DeviceState state = stateData.getState();
long ts = System.currentTimeMillis();
state.setActive(true);
stateData.getState().setLastActivityTime(ts);
pushRuleEngineMessage(stateData, ACTIVITY_EVENT);
saveAttribute(deviceId, LAST_ACTIVITY_TIME, ts);
saveAttribute(deviceId, ACTIVITY_STATE, state.isActive());
}
}
private void onInactivityTimeoutUpdate(DeviceId deviceId, long inactivityTimeout) {
if (inactivityTimeout == 0L) {
return;
}
DeviceStateData stateData = deviceStates.get(deviceId);
if (stateData != null) {
long ts = System.currentTimeMillis();
DeviceState state = stateData.getState();
state.setInactivityTimeout(inactivityTimeout);
boolean oldActive = state.isActive();
state.setActive(ts < state.getLastActivityTime() + state.getInactivityTimeout());
if (!oldActive && state.isActive()) {
saveAttribute(deviceId, ACTIVITY_STATE, state.isActive());
}
}
}
private void onDeviceAddedSync(Device device) {
Futures.addCallback(fetchDeviceState(device), new FutureCallback<DeviceStateData>() {
@Override
public void onSuccess(@Nullable DeviceStateData state) {
addDeviceUsingState(state);
}
@Override
public void onFailure(Throwable t) {
log.warn("Failed to register device to the state service", t);
}
});
}
private void onDeviceUpdatedSync(Device device) {
DeviceStateData stateData = deviceStates.get(device.getId());
if (stateData != null) {
TbMsgMetaData md = new TbMsgMetaData();
md.putValue("deviceName", device.getName());
md.putValue("deviceType", device.getType());
stateData.setMetaData(md);
}
}
private void onDeviceDeleted(TenantId tenantId, DeviceId deviceId) {
deviceStates.remove(deviceId);
Set<DeviceId> deviceIds = tenantDevices.get(tenantId);
if (deviceIds != null) {
deviceIds.remove(deviceId);
if (deviceIds.isEmpty()) {
tenantDevices.remove(tenantId);
}
}
}
private ListenableFuture<DeviceStateData> fetchDeviceState(Device device) {
ListenableFuture<List<AttributeKvEntry>> attributes = attributesService.find(device.getId(), DataConstants.SERVER_SCOPE, PERSISTENT_ATTRIBUTES);
return Futures.transform(attributes, new Function<List<AttributeKvEntry>, DeviceStateData>() {
@Nullable
@Override
public DeviceStateData apply(@Nullable List<AttributeKvEntry> attributes) {
long lastActivityTime = getAttributeValue(attributes, LAST_ACTIVITY_TIME, 0L);
long inactivityAlarmTime = getAttributeValue(attributes, INACTIVITY_ALARM_TIME, 0L);
long inactivityTimeout = getAttributeValue(attributes, INACTIVITY_TIMEOUT, TimeUnit.SECONDS.toMillis(defaultInactivityTimeoutInSec));
boolean active = System.currentTimeMillis() < lastActivityTime + inactivityTimeout;
DeviceState deviceState = DeviceState.builder()
.active(active)
.lastConnectTime(getAttributeValue(attributes, LAST_CONNECT_TIME, 0L))
.lastDisconnectTime(getAttributeValue(attributes, LAST_DISCONNECT_TIME, 0L))
.lastActivityTime(lastActivityTime)
.lastInactivityAlarmTime(inactivityAlarmTime)
.inactivityTimeout(inactivityTimeout)
.build();
TbMsgMetaData md = new TbMsgMetaData();
md.putValue("deviceName", device.getName());
md.putValue("deviceType", device.getType());
return DeviceStateData.builder()
.tenantId(device.getTenantId())
.deviceId(device.getId())
.metaData(md)
.state(deviceState).build();
}
});
}
private long getLastPersistTime(List<AttributeKvEntry> attributes) {
return attributes.stream().map(AttributeKvEntry::getLastUpdateTs).max(Long::compare).orElse(0L);
}
private long getAttributeValue(List<AttributeKvEntry> attributes, String attributeName, long defaultValue) {
for (AttributeKvEntry attribute : attributes) {
if (attribute.getKey().equals(attributeName)) {
return attribute.getLongValue().orElse(defaultValue);
}
}
return defaultValue;
}
private void pushRuleEngineMessage(DeviceStateData stateData, String msgType) {
DeviceState state = stateData.getState();
try {
TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), msgType, stateData.getDeviceId(), stateData.getMetaData(), TbMsgDataType.JSON
, json.writeValueAsString(state)
, null, null, 0L);
actorService.onMsg(new ServiceToRuleEngineMsg(stateData.getTenantId(), tbMsg));
} catch (Exception e) {
log.warn("[{}] Failed to push inactivity alarm: {}", stateData.getDeviceId(), state, e);
}
}
private void saveAttribute(DeviceId deviceId, String key, long value) {
tsSubService.saveAttrAndNotify(deviceId, DataConstants.SERVER_SCOPE, key, value, new AttributeSaveCallback(deviceId, key, value));
}
private void saveAttribute(DeviceId deviceId, String key, boolean value) {
tsSubService.saveAttrAndNotify(deviceId, DataConstants.SERVER_SCOPE, key, value, new AttributeSaveCallback(deviceId, key, value));
}
private class AttributeSaveCallback implements FutureCallback<Void> {
private final DeviceId deviceId;
private final String key;
private final Object value;
AttributeSaveCallback(DeviceId deviceId, String key, Object value) {
this.deviceId = deviceId;
this.key = key;
this.value = value;
}
@Override
public void onSuccess(@Nullable Void result) {
log.trace("[{}] Successfully updated attribute [{}] with value [{}]", deviceId, key, value);
}
@Override
public void onFailure(Throwable t) {
log.warn("[{}] Failed to update attribute [{}] with value [{}]", deviceId, key, value, t);
}
}
}

35
application/src/main/java/org/thingsboard/server/service/state/DeviceState.java

@ -0,0 +1,35 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.state;
import lombok.Builder;
import lombok.Data;
/**
* Created by ashvayka on 01.05.18.
*/
@Data
@Builder
public class DeviceState {
private boolean active;
private long lastConnectTime;
private long lastActivityTime;
private long lastDisconnectTime;
private long lastInactivityAlarmTime;
private long inactivityTimeout;
}

37
application/src/main/java/org/thingsboard/server/service/state/DeviceStateData.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.service.state;
import lombok.Builder;
import lombok.Data;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.TbMsgMetaData;
/**
* Created by ashvayka on 01.05.18.
*/
@Data
@Builder
class DeviceStateData {
private final TenantId tenantId;
private final DeviceId deviceId;
private TbMsgMetaData metaData;
private final DeviceState state;
}

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

@ -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.
*/
package org.thingsboard.server.service.state;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.id.DeviceId;
import java.util.Optional;
/**
* Created by ashvayka on 01.05.18.
*/
public interface DeviceStateService {
void onDeviceAdded(Device device);
void onDeviceUpdated(Device device);
void onDeviceDeleted(Device device);
void onDeviceConnect(DeviceId deviceId);
void onDeviceActivity(DeviceId deviceId);
void onDeviceDisconnect(DeviceId deviceId);
void onDeviceInactivityTimeoutUpdate(DeviceId deviceId, long inactivityTimeout);
Optional<DeviceState> getDeviceState(DeviceId deviceId);
}

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

@ -20,11 +20,20 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.common.data.kv.DoubleDataEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.dao.attributes.AttributesService;
@ -34,11 +43,14 @@ import org.thingsboard.server.extensions.core.plugin.telemetry.sub.Subscription;
import org.thingsboard.server.extensions.core.plugin.telemetry.sub.SubscriptionState;
import org.thingsboard.server.extensions.core.plugin.telemetry.sub.SubscriptionUpdate;
import org.thingsboard.server.service.cluster.routing.ClusterRoutingService;
import org.thingsboard.server.service.state.DefaultDeviceStateService;
import org.thingsboard.server.service.state.DeviceStateService;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@ -70,6 +82,10 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
@Autowired
private ClusterRoutingService routingService;
@Autowired
@Lazy
private DeviceStateService stateService;
private ExecutorService tsCallBackExecutor;
private ExecutorService wsCallBackExecutor;
@ -149,10 +165,41 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
addWsCallback(saveFuture, success -> onAttributesUpdate(entityId, scope, attributes));
}
@Override
public void saveAttrAndNotify(EntityId entityId, String scope, String key, long value, FutureCallback<Void> callback) {
saveAndNotify(entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new LongDataEntry(key, value)
, System.currentTimeMillis())), callback);
}
@Override
public void saveAttrAndNotify(EntityId entityId, String scope, String key, String value, FutureCallback<Void> callback) {
saveAndNotify(entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry(key, value)
, System.currentTimeMillis())), callback);
}
@Override
public void saveAttrAndNotify(EntityId entityId, String scope, String key, double value, FutureCallback<Void> callback) {
saveAndNotify(entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new DoubleDataEntry(key, value)
, System.currentTimeMillis())), callback);
}
@Override
public void saveAttrAndNotify(EntityId entityId, String scope, String key, boolean value, FutureCallback<Void> callback) {
saveAndNotify(entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new BooleanDataEntry(key, value)
, System.currentTimeMillis())), callback);
}
private void onAttributesUpdate(EntityId entityId, String scope, List<AttributeKvEntry> attributes) {
Optional<ServerAddress> serverAddress = routingService.resolveById(entityId);
if (!serverAddress.isPresent()) {
onLocalAttributesUpdate(entityId, scope, attributes);
if (entityId.getEntityType() == EntityType.DEVICE && DataConstants.SERVER_SCOPE.equalsIgnoreCase(scope)) {
for (AttributeKvEntry attribute : attributes) {
if (attribute.getKey().equals(DefaultDeviceStateService.INACTIVITY_TIMEOUT)) {
stateService.onDeviceInactivityTimeoutUpdate(new DeviceId(entityId.getId()), attribute.getLongValue().orElse(0L));
}
}
}
} else {
// rpcHandler.onAttributesUpdate(ctx, serverAddress.get(), entityId, entries);
}

3
application/src/main/proto/cluster.proto

@ -61,7 +61,6 @@ message ConnectRpcMessage {
}
message ToDeviceRpcRequestRpcMessage {
PluginAddress address = 1;
Uid deviceTenantId = 2;
Uid deviceId = 3;
@ -73,8 +72,6 @@ message ToDeviceRpcRequestRpcMessage {
}
message ToPluginRpcResponseRpcMessage {
PluginAddress address = 1;
Uid msgId = 2;
string response = 3;
string error = 4;

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

@ -207,24 +207,15 @@ actors:
sync:
# Default timeout for processing request using synchronous session (HTTP, CoAP) in milliseconds
timeout: "${ACTORS_SESSION_SYNC_TIMEOUT:10000}"
plugin:
# Default timeout for termination of the plugin actor after it is stopped
termination.delay: "${ACTORS_PLUGIN_TERMINATION_DELAY:60000}"
# Default timeout for processing of particular message by particular plugin
processing.timeout: "${ACTORS_PLUGIN_TIMEOUT:60000}"
# Errors for particular actor are persisted once per specified amount of milliseconds
error_persist_frequency: "${ACTORS_PLUGIN_ERROR_FREQUENCY:3000}"
rule:
# Default timeout for termination of the rule actor after it is stopped
termination.delay: "${ACTORS_RULE_TERMINATION_DELAY:30000}"
# Errors for particular actor are persisted once per specified amount of milliseconds
error_persist_frequency: "${ACTORS_RULE_ERROR_FREQUENCY:3000}"
# Specify thread pool size for database request callbacks executor service
db_callback_thread_pool_size: "${ACTORS_RULE_DB_CALLBACK_THREAD_POOL_SIZE:1}"
# Specify thread pool size for javascript executor service
js_thread_pool_size: "${ACTORS_RULE_JS_THREAD_POOL_SIZE:10}"
# Specify thread pool size for mail sender executor service
mail_thread_pool_size: "${ACTORS_RULE_MAIL_THREAD_POOL_SIZE:10}"
# Specify thread pool size for external call service
external_call_thread_pool_size: "${ACTORS_RULE_EXTERNAL_CALL_THREAD_POOL_SIZE:10}"
chain:
# Errors for particular actor are persisted once per specified amount of milliseconds
error_persist_frequency: "${ACTORS_RULE_CHAIN_ERROR_FREQUENCY:3000}"
@ -235,6 +226,13 @@ actors:
# Enable/disable actor statistics
enabled: "${ACTORS_STATISTICS_ENABLED:true}"
persist_frequency: "${ACTORS_STATISTICS_PERSIST_FREQUENCY:3600000}"
queue:
# Enable/disable persistence of un-processed messages to the queue
enabled: "${ACTORS_QUEUE_ENABLED:true}"
# Maximum allowed timeout for persistence into the queue
timeout: "${ACTORS_QUEUE_PERSISTENCE_TIMEOUT:30000}"
client_side_rpc:
timeout: "${CLIENT_SIDE_RPC_TIMEOUT:60000}"
cache:
# caffeine or redis
@ -306,7 +304,9 @@ spring:
rule:
queue:
msg_partitioning: "${QUEUE_MSG_PARTITIONING:HOURS}"
type: "memory"
max_size: 10000
# PostgreSQL DAO Configuration
#spring:
@ -359,4 +359,11 @@ audit_log:
host: "${AUDIT_LOG_SINK_HOST:localhost}"
port: "${AUDIT_LOG_SINK_POST:9200}"
user_name: "${AUDIT_LOG_SINK_USER_NAME:}"
password: "${AUDIT_LOG_SINK_PASSWORD:}"
password: "${AUDIT_LOG_SINK_PASSWORD:}"
state:
defaultInactivityTimeoutInSec: 10
defaultStateCheckIntervalInSec: 10
# TODO in v2.1
# defaultStatePersistenceIntervalInSec: 60
# defaultStatePersistencePack: 100

27
application/src/test/java/org/thingsboard/server/actors/ActorsTestSuite.java

@ -1,27 +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;
import org.junit.extensions.cpsuite.ClasspathSuite;
import org.junit.runner.RunWith;
/**
* @author Andrew Shvayka
*/
@RunWith(ClasspathSuite.class)
@ClasspathSuite.ClassnameFilters({"org.thingsboard.server.actors.*Test"})
public class ActorsTestSuite {
}

245
application/src/test/java/org/thingsboard/server/actors/DefaultActorServiceTest.java

@ -1,245 +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;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.*;
import com.google.common.util.concurrent.Futures;
import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.common.data.id.*;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.session.*;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.gen.discovery.ServerInstanceProtos;
import org.thingsboard.server.service.cluster.discovery.DiscoveryService;
import org.thingsboard.server.service.cluster.discovery.ServerInstance;
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.common.transport.auth.DeviceAuthResult;
import org.thingsboard.server.common.transport.auth.DeviceAuthService;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.plugin.PluginMetaData;
import org.thingsboard.server.common.data.rule.RuleMetaData;
import org.thingsboard.server.common.data.security.DeviceCredentialsFilter;
import org.thingsboard.server.common.data.security.DeviceTokenCredentials;
import org.thingsboard.server.common.msg.core.BasicTelemetryUploadRequest;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.plugin.PluginService;
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.core.plugin.telemetry.TelemetryStoragePlugin;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.test.util.ReflectionTestUtils;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
public class DefaultActorServiceTest {
private static final TenantId SYSTEM_TENANT = new TenantId(ModelConstants.NULL_UUID);
private static final String PLUGIN_ID = "9fb2e951-e298-4acb-913a-db69af8a15f4";
private static final String FILTERS_CONFIGURATION =
"[{\"clazz\":\"org.thingsboard.server.extensions.core.filter.MsgTypeFilter\", \"name\":\"TelemetryFilter\", \"configuration\": {\"messageTypes\":[\"POST_TELEMETRY\",\"POST_ATTRIBUTES\",\"GET_ATTRIBUTES\"]}}]";
private static final String ACTION_CONFIGURATION = "{\"pluginToken\":\"telemetry\", \"clazz\":\"org.thingsboard.server.extensions.core.action.telemetry.TelemetryPluginAction\", \"name\":\"TelemetryMsgConverterAction\", \"configuration\":{}}";
private static final String PLUGIN_CONFIGURATION = "{}";
private DefaultActorService actorService;
private ActorSystemContext actorContext;
private PluginService pluginService;
private RuleService ruleService;
private DeviceAuthService deviceAuthService;
private DeviceService deviceService;
private TimeseriesService tsService;
private TenantService tenantService;
private ClusterRpcService rpcService;
private DiscoveryService discoveryService;
private ClusterRoutingService routingService;
private AttributesService attributesService;
private ComponentDiscoveryService componentService;
private EventService eventService;
private ServerInstance serverInstance;
private RuleMetaData ruleMock;
private PluginMetaData pluginMock;
private RuleId ruleId = new RuleId(UUID.randomUUID());
private PluginId pluginId = new PluginId(UUID.fromString(PLUGIN_ID));
private TenantId tenantId = new TenantId(UUID.randomUUID());
@Before
public void before() throws Exception {
actorService = new DefaultActorService();
actorContext = new ActorSystemContext();
tenantService = mock(TenantService.class);
pluginService = mock(PluginService.class);
ruleService = mock(RuleService.class);
deviceAuthService = mock(DeviceAuthService.class);
deviceService = mock(DeviceService.class);
tsService = mock(TimeseriesService.class);
rpcService = mock(ClusterRpcService.class);
discoveryService = mock(DiscoveryService.class);
routingService = mock(ClusterRoutingService.class);
attributesService = mock(AttributesService.class);
componentService = mock(ComponentDiscoveryService.class);
eventService = mock(EventService.class);
serverInstance = new ServerInstance(ServerInstanceProtos.ServerInfo.newBuilder().setHost("localhost").setPort(8080).build());
ReflectionTestUtils.setField(actorService, "actorContext", actorContext);
ReflectionTestUtils.setField(actorService, "rpcService", rpcService);
ReflectionTestUtils.setField(actorService, "discoveryService", discoveryService);
ReflectionTestUtils.setField(actorContext, "syncSessionTimeout", 10000L);
ReflectionTestUtils.setField(actorContext, "pluginActorTerminationDelay", 10000L);
ReflectionTestUtils.setField(actorContext, "pluginErrorPersistFrequency", 10000L);
ReflectionTestUtils.setField(actorContext, "ruleActorTerminationDelay", 10000L);
ReflectionTestUtils.setField(actorContext, "ruleErrorPersistFrequency", 10000L);
ReflectionTestUtils.setField(actorContext, "pluginProcessingTimeout", 60000L);
ReflectionTestUtils.setField(actorContext, "tenantService", tenantService);
ReflectionTestUtils.setField(actorContext, "pluginService", pluginService);
ReflectionTestUtils.setField(actorContext, "ruleService", ruleService);
ReflectionTestUtils.setField(actorContext, "deviceAuthService", deviceAuthService);
ReflectionTestUtils.setField(actorContext, "deviceService", deviceService);
ReflectionTestUtils.setField(actorContext, "tsService", tsService);
ReflectionTestUtils.setField(actorContext, "rpcService", rpcService);
ReflectionTestUtils.setField(actorContext, "discoveryService", discoveryService);
ReflectionTestUtils.setField(actorContext, "tsService", tsService);
ReflectionTestUtils.setField(actorContext, "routingService", routingService);
ReflectionTestUtils.setField(actorContext, "attributesService", attributesService);
ReflectionTestUtils.setField(actorContext, "componentService", componentService);
ReflectionTestUtils.setField(actorContext, "eventService", eventService);
when(routingService.resolveById((EntityId) any())).thenReturn(Optional.empty());
when(discoveryService.getCurrentServer()).thenReturn(serverInstance);
ruleMock = mock(RuleMetaData.class);
when(ruleMock.getId()).thenReturn(ruleId);
when(ruleMock.getState()).thenReturn(ComponentLifecycleState.ACTIVE);
when(ruleMock.getPluginToken()).thenReturn("telemetry");
TextPageData<RuleMetaData> systemRules = new TextPageData<>(Collections.emptyList(), null, false);
TextPageData<RuleMetaData> tenantRules = new TextPageData<>(Collections.singletonList(ruleMock), null, false);
when(ruleService.findSystemRules(any())).thenReturn(systemRules);
when(ruleService.findTenantRules(any(), any())).thenReturn(tenantRules);
when(ruleService.findRuleById(ruleId)).thenReturn(ruleMock);
pluginMock = mock(PluginMetaData.class);
when(pluginMock.getTenantId()).thenReturn(SYSTEM_TENANT);
when(pluginMock.getId()).thenReturn(pluginId);
when(pluginMock.getState()).thenReturn(ComponentLifecycleState.ACTIVE);
TextPageData<PluginMetaData> systemPlugins = new TextPageData<>(Collections.singletonList(pluginMock), null, false);
TextPageData<PluginMetaData> tenantPlugins = new TextPageData<>(Collections.emptyList(), null, false);
when(pluginService.findSystemPlugins(any())).thenReturn(systemPlugins);
when(pluginService.findTenantPlugins(any(), any())).thenReturn(tenantPlugins);
when(pluginService.findPluginByApiToken("telemetry")).thenReturn(pluginMock);
when(pluginService.findPluginById(pluginId)).thenReturn(pluginMock);
TextPageData<Tenant> tenants = new TextPageData<>(Collections.emptyList(), null, false);
when(tenantService.findTenants(any())).thenReturn(tenants);
}
private void initActorSystem() {
actorService.initActorSystem();
}
@After
public void after() {
actorService.stopActorSystem();
}
@Test
public void testBasicPostWithSyncSession() throws Exception {
SessionContext ssnCtx = mock(SessionContext.class);
KvEntry entry1 = new StringDataEntry("key1", "value1");
KvEntry entry2 = new StringDataEntry("key2", "value2");
BasicTelemetryUploadRequest telemetry = new BasicTelemetryUploadRequest();
long ts = 42;
telemetry.add(ts, entry1);
telemetry.add(ts, entry2);
BasicAdaptorToSessionActorMsg msg = new BasicAdaptorToSessionActorMsg(ssnCtx, telemetry);
DeviceId deviceId = new DeviceId(UUID.randomUUID());
DeviceCredentialsFilter filter = new DeviceTokenCredentials("token1");
Device device = mock(Device.class);
when(device.getId()).thenReturn(deviceId);
when(device.getTenantId()).thenReturn(tenantId);
when(ssnCtx.getSessionId()).thenReturn(new DummySessionID("session1"));
when(ssnCtx.getSessionType()).thenReturn(SessionType.SYNC);
when(deviceAuthService.process(filter)).thenReturn(DeviceAuthResult.of(deviceId));
when(deviceService.findDeviceById(deviceId)).thenReturn(device);
ObjectMapper ruleMapper = new ObjectMapper();
when(ruleMock.getFilters()).thenReturn(ruleMapper.readTree(FILTERS_CONFIGURATION));
when(ruleMock.getAction()).thenReturn(ruleMapper.readTree(ACTION_CONFIGURATION));
ComponentDescriptor filterComp = new ComponentDescriptor();
filterComp.setClazz("org.thingsboard.server.extensions.core.filter.MsgTypeFilter");
filterComp.setType(ComponentType.FILTER);
when(componentService.getComponent("org.thingsboard.server.extensions.core.filter.MsgTypeFilter"))
.thenReturn(Optional.of(filterComp));
ComponentDescriptor actionComp = new ComponentDescriptor();
actionComp.setClazz("org.thingsboard.server.extensions.core.action.telemetry.TelemetryPluginAction");
actionComp.setType(ComponentType.ACTION);
when(componentService.getComponent("org.thingsboard.server.extensions.core.action.telemetry.TelemetryPluginAction"))
.thenReturn(Optional.of(actionComp));
ObjectMapper pluginMapper = new ObjectMapper();
JsonNode pluginAdditionalInfo = pluginMapper.readTree(PLUGIN_CONFIGURATION);
when(pluginMock.getConfiguration()).thenReturn(pluginAdditionalInfo);
when(pluginMock.getClazz()).thenReturn(TelemetryStoragePlugin.class.getName());
when(attributesService.findAll(deviceId, DataConstants.CLIENT_SCOPE)).thenReturn(Futures.immediateFuture(Collections.emptyList()));
when(attributesService.findAll(deviceId, DataConstants.SHARED_SCOPE)).thenReturn(Futures.immediateFuture(Collections.emptyList()));
when(attributesService.findAll(deviceId, DataConstants.SERVER_SCOPE)).thenReturn(Futures.immediateFuture(Collections.emptyList()));
initActorSystem();
Thread.sleep(1000);
actorService.process(new BasicToDeviceActorSessionMsg(device, msg));
// Check that device data was saved to DB;
List<TsKvEntry> expected = new ArrayList<>();
expected.add(new BasicTsKvEntry(ts, entry1));
expected.add(new BasicTsKvEntry(ts, entry2));
verify(tsService, Mockito.timeout(5000)).save(deviceId, expected, 0L);
}
}

63
application/src/test/java/org/thingsboard/server/actors/DummySessionID.java

@ -1,63 +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;
import org.thingsboard.server.common.data.id.SessionId;
public class DummySessionID implements SessionId {
@Override
public String toString() {
return id;
}
private final String id;
public DummySessionID(String id) {
this.id = id;
}
@Override
public String toUidStr() {
return id;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.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;
DummySessionID other = (DummySessionID) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
}

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

@ -16,6 +16,8 @@
package org.thingsboard.server.controller;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Event;
import org.thingsboard.server.common.data.id.EntityId;
@ -25,12 +27,22 @@ import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.dao.queue.MsgQueue;
import org.thingsboard.server.dao.rule.RuleChainService;
import java.io.IOException;
/**
* Created by ashvayka on 20.03.18.
*/
public class AbstractRuleEngineControllerTest extends AbstractControllerTest {
@Autowired
protected RuleChainService ruleChainService;
@Autowired
protected MsgQueue msgQueue;
protected RuleChain saveRuleChain(RuleChain ruleChain) throws Exception {
return doPost("/api/ruleChain", ruleChain, RuleChain.class);
}
@ -53,4 +65,13 @@ public class AbstractRuleEngineControllerTest extends AbstractControllerTest {
new TypeReference<TimePageData<Event>>() {
}, pageLink, entityId.getEntityType(), entityId.getId(), DataConstants.DEBUG_RULE_NODE, tenantId.getId());
}
protected JsonNode getMetadata(Event outEvent) {
String metaDataStr = outEvent.getBody().get("metadata").asText();
try {
return mapper.readTree(metaDataStr);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

21
application/src/test/java/org/thingsboard/server/controller/BaseComponentDescriptorControllerTest.java

@ -20,6 +20,7 @@ import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.rule.engine.filter.TbJsFilterNode;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
@ -35,7 +36,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
public abstract class BaseComponentDescriptorControllerTest extends AbstractControllerTest {
private static final int AMOUNT_OF_DEFAULT_PLUGINS_DESCRIPTORS = 5;
private static final int AMOUNT_OF_DEFAULT_FILTER_NODES = 4;
private Tenant savedTenant;
private User tenantAdmin;
@ -69,38 +70,28 @@ public abstract class BaseComponentDescriptorControllerTest extends AbstractCont
@Test
public void testGetByClazz() throws Exception {
ComponentDescriptor descriptor =
doGet("/api/component/" + TelemetryStoragePlugin.class.getName(), ComponentDescriptor.class);
doGet("/api/component/" + TbJsFilterNode.class.getName(), ComponentDescriptor.class);
Assert.assertNotNull(descriptor);
Assert.assertNotNull(descriptor.getId());
Assert.assertNotNull(descriptor.getName());
Assert.assertEquals(ComponentScope.TENANT, descriptor.getScope());
Assert.assertEquals(ComponentType.PLUGIN, descriptor.getType());
Assert.assertEquals(ComponentType.FILTER, descriptor.getType());
Assert.assertEquals(descriptor.getClazz(), descriptor.getClazz());
}
@Test
public void testGetByType() throws Exception {
List<ComponentDescriptor> descriptors = readResponse(
doGet("/api/components/" + ComponentType.PLUGIN).andExpect(status().isOk()), new TypeReference<List<ComponentDescriptor>>() {
doGet("/api/components/" + ComponentType.FILTER).andExpect(status().isOk()), new TypeReference<List<ComponentDescriptor>>() {
});
Assert.assertNotNull(descriptors);
Assert.assertEquals(AMOUNT_OF_DEFAULT_PLUGINS_DESCRIPTORS, descriptors.size());
Assert.assertTrue(descriptors.size() >= AMOUNT_OF_DEFAULT_FILTER_NODES);
for (ComponentType type : ComponentType.values()) {
doGet("/api/components/" + type).andExpect(status().isOk());
}
}
@Test
public void testGetActionsByType() throws Exception {
List<ComponentDescriptor> descriptors = readResponse(
doGet("/api/components/actions/" + TelemetryStoragePlugin.class.getName()).andExpect(status().isOk()), new TypeReference<List<ComponentDescriptor>>() {
});
Assert.assertNotNull(descriptors);
Assert.assertEquals(1, descriptors.size());
Assert.assertEquals(TelemetryPluginAction.class.getName(), descriptors.get(0).getClazz());
}
}

232
application/src/test/java/org/thingsboard/server/controller/BasePluginControllerTest.java

@ -1,232 +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.controller;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.plugin.PluginMetaData;
import org.thingsboard.server.common.data.rule.RuleMetaData;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.extensions.core.plugin.telemetry.TelemetryStoragePlugin;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
public abstract class BasePluginControllerTest extends AbstractControllerTest {
private IdComparator<PluginMetaData> idComparator = new IdComparator<>();
private final ObjectMapper mapper = new ObjectMapper();
private Tenant savedTenant;
private User tenantAdmin;
@Before
public void beforeTest() throws Exception {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
tenantAdmin.setTenantId(savedTenant.getId());
tenantAdmin.setEmail("tenant2@thingsboard.org");
tenantAdmin.setFirstName("Joe");
tenantAdmin.setLastName("Downs");
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
}
@After
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
}
@Test
public void testSavePlugin() throws Exception {
PluginMetaData plugin = new PluginMetaData();
doPost("/api/plugin", plugin).andExpect(status().isBadRequest());
plugin.setName("My plugin");
doPost("/api/plugin", plugin).andExpect(status().isBadRequest());
plugin.setApiToken("myplugin");
doPost("/api/plugin", plugin).andExpect(status().isBadRequest());
plugin.setConfiguration(mapper.readTree("{}"));
doPost("/api/plugin", plugin).andExpect(status().isBadRequest());
plugin.setClazz(TelemetryStoragePlugin.class.getName());
PluginMetaData savedPlugin = doPost("/api/plugin", plugin, PluginMetaData.class);
Assert.assertNotNull(savedPlugin);
Assert.assertNotNull(savedPlugin.getId());
Assert.assertTrue(savedPlugin.getCreatedTime() > 0);
Assert.assertEquals(savedTenant.getId(), savedPlugin.getTenantId());
}
@Test
public void testFindPluginById() throws Exception {
PluginMetaData plugin = new PluginMetaData();
plugin.setName("My plugin");
plugin.setApiToken("myplugin");
plugin.setConfiguration(mapper.readTree("{}"));
plugin.setClazz(TelemetryStoragePlugin.class.getName());
PluginMetaData savedPlugin = doPost("/api/plugin", plugin, PluginMetaData.class);
PluginMetaData foundPlugin = doGet("/api/plugin/" + savedPlugin.getId().getId().toString(), PluginMetaData.class);
Assert.assertNotNull(foundPlugin);
Assert.assertEquals(savedPlugin, foundPlugin);
}
@Test
public void testActivatePlugin() throws Exception {
PluginMetaData plugin = new PluginMetaData();
plugin.setName("My plugin");
plugin.setApiToken("myplugin");
plugin.setConfiguration(mapper.readTree("{}"));
plugin.setClazz(TelemetryStoragePlugin.class.getName());
PluginMetaData savedPlugin = doPost("/api/plugin", plugin, PluginMetaData.class);
doPost("/api/plugin/" + savedPlugin.getId().getId().toString() + "/activate").andExpect(status().isOk());
}
@Test
public void testSuspendPlugin() throws Exception {
PluginMetaData plugin = new PluginMetaData();
plugin.setName("My plugin");
plugin.setApiToken("myplugin");
plugin.setConfiguration(mapper.readTree("{}"));
plugin.setClazz(TelemetryStoragePlugin.class.getName());
PluginMetaData savedPlugin = doPost("/api/plugin", plugin, PluginMetaData.class);
doPost("/api/plugin/" + savedPlugin.getId().getId().toString() + "/activate").andExpect(status().isOk());
RuleMetaData rule = BaseRuleControllerTest.createRuleMetaData(savedPlugin);
RuleMetaData savedRule = doPost("/api/rule", rule, RuleMetaData.class);
doPost("/api/rule/" + savedRule.getId().getId().toString() + "/activate").andExpect(status().isOk());
doPost("/api/plugin/" + savedPlugin.getId().getId().toString() + "/suspend").andExpect(status().isBadRequest());
doPost("/api/rule/" + savedRule.getId().getId().toString() + "/suspend").andExpect(status().isOk());
doPost("/api/plugin/" + savedPlugin.getId().getId().toString() + "/suspend").andExpect(status().isOk());
}
@Test
public void testDeletePluginById() throws Exception {
PluginMetaData plugin = new PluginMetaData();
plugin.setName("My plugin");
plugin.setApiToken("myplugin");
plugin.setConfiguration(mapper.readTree("{}"));
plugin.setClazz(TelemetryStoragePlugin.class.getName());
PluginMetaData savedPlugin = doPost("/api/plugin", plugin, PluginMetaData.class);
RuleMetaData rule = BaseRuleControllerTest.createRuleMetaData(savedPlugin);
RuleMetaData savedRule = doPost("/api/rule", rule, RuleMetaData.class);
doDelete("/api/plugin/" + savedPlugin.getId().getId()).andExpect(status().isBadRequest());
doDelete("/api/rule/" + savedRule.getId().getId()).andExpect(status().isOk());
doDelete("/api/plugin/" + savedPlugin.getId().getId()).andExpect(status().isOk());
doGet("/api/plugin/" + savedPlugin.getId().getId().toString()).andExpect(status().isNotFound());
}
@Test
public void testFindPluginByToken() throws Exception {
PluginMetaData plugin = new PluginMetaData();
plugin.setName("My plugin");
plugin.setApiToken("myplugin");
plugin.setConfiguration(mapper.readTree("{}"));
plugin.setClazz(TelemetryStoragePlugin.class.getName());
PluginMetaData savedPlugin = doPost("/api/plugin", plugin, PluginMetaData.class);
PluginMetaData foundPlugin = doGet("/api/plugin/token/" + "myplugin", PluginMetaData.class);
Assert.assertNotNull(foundPlugin);
Assert.assertEquals(savedPlugin, foundPlugin);
}
@Test
public void testFindCurrentTenantPlugins() throws Exception {
List<PluginMetaData> plugins = testPluginsCreation("/api/plugin");
for (PluginMetaData plugin : plugins) {
doDelete("/api/plugin/" + plugin.getId().getId()).andExpect(status().isOk());
}
}
@Test
public void testFindSystemPlugins() throws Exception {
loginSysAdmin();
List<PluginMetaData> plugins = testPluginsCreation("/api/plugin/system");
for (PluginMetaData plugin : plugins) {
doDelete("/api/plugin/" + plugin.getId().getId()).andExpect(status().isOk());
}
}
private List<PluginMetaData> testPluginsCreation(String url) throws Exception {
List<PluginMetaData> plugins = new ArrayList<>();
for (int i = 0; i < 111; i++) {
PluginMetaData plugin = new PluginMetaData();
plugin.setName("My plugin");
plugin.setApiToken("myplugin" + i);
plugin.setConfiguration(mapper.readTree("{}"));
plugin.setClazz(TelemetryStoragePlugin.class.getName());
plugins.add(doPost("/api/plugin", plugin, PluginMetaData.class));
}
List<PluginMetaData> loadedPlugins = new ArrayList<>();
TextPageLink pageLink = new TextPageLink(23);
TextPageData<PluginMetaData> pageData;
do {
pageData = doGetTypedWithPageLink(url + "?",
new TypeReference<TextPageData<PluginMetaData>>() {
}, pageLink);
loadedPlugins.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink();
}
} while (pageData.hasNext());
loadedPlugins = loadedPlugins.stream()
.filter(p -> !p.getName().equals("System Telemetry Plugin"))
.filter(p -> !p.getName().equals("Mail Sender Plugin"))
.filter(p -> !p.getName().equals("System RPC Plugin"))
.collect(Collectors.toList());
Collections.sort(plugins, idComparator);
Collections.sort(loadedPlugins, idComparator);
Assert.assertEquals(plugins, loadedPlugins);
return loadedPlugins;
}
}

247
application/src/test/java/org/thingsboard/server/controller/BaseRuleControllerTest.java

@ -1,247 +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.controller;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.plugin.PluginMetaData;
import org.thingsboard.server.common.data.rule.RuleMetaData;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.extensions.core.plugin.telemetry.TelemetryStoragePlugin;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
public abstract class BaseRuleControllerTest extends AbstractControllerTest {
private IdComparator<RuleMetaData> idComparator = new IdComparator<>();
private static final ObjectMapper mapper = new ObjectMapper();
private Tenant savedTenant;
private User tenantAdmin;
private PluginMetaData sysPlugin;
private PluginMetaData tenantPlugin;
@Before
public void beforeTest() throws Exception {
loginSysAdmin();
sysPlugin = new PluginMetaData();
sysPlugin.setName("Sys plugin");
sysPlugin.setApiToken("sysplugin");
sysPlugin.setConfiguration(mapper.readTree("{}"));
sysPlugin.setClazz(TelemetryStoragePlugin.class.getName());
sysPlugin = doPost("/api/plugin", sysPlugin, PluginMetaData.class);
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
tenantAdmin.setTenantId(savedTenant.getId());
tenantAdmin.setEmail("tenant2@thingsboard.org");
tenantAdmin.setFirstName("Joe");
tenantAdmin.setLastName("Downs");
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
tenantPlugin = new PluginMetaData();
tenantPlugin.setName("My plugin");
tenantPlugin.setApiToken("myplugin");
tenantPlugin.setConfiguration(mapper.readTree("{}"));
tenantPlugin.setClazz(TelemetryStoragePlugin.class.getName());
tenantPlugin = doPost("/api/plugin", tenantPlugin, PluginMetaData.class);
}
@After
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
doDelete("/api/plugin/" + sysPlugin.getId().getId()).andExpect(status().isOk());
}
@Test
public void testSaveRule() throws Exception {
RuleMetaData rule = new RuleMetaData();
doPost("/api/rule", rule).andExpect(status().isBadRequest());
rule.setName("My Rule");
doPost("/api/rule", rule).andExpect(status().isBadRequest());
rule.setPluginToken(tenantPlugin.getApiToken());
doPost("/api/rule", rule).andExpect(status().isBadRequest());
rule.setFilters(mapper.readTree("[{\"clazz\":\"org.thingsboard.server.extensions.core.filter.MsgTypeFilter\", " +
"\"name\":\"TelemetryFilter\", " +
"\"configuration\": {\"messageTypes\":[\"POST_TELEMETRY\",\"POST_ATTRIBUTES\",\"GET_ATTRIBUTES\"]}}]"));
doPost("/api/rule", rule).andExpect(status().isBadRequest());
rule.setAction(mapper.readTree("{\"clazz\":\"org.thingsboard.server.extensions.core.action.telemetry.TelemetryPluginAction\", \"name\":\"TelemetryMsgConverterAction\", \"configuration\":{\"timeUnit\":\"DAYS\", \"ttlValue\":1}}"));
RuleMetaData savedRule = doPost("/api/rule", rule, RuleMetaData.class);
Assert.assertNotNull(savedRule);
Assert.assertNotNull(savedRule.getId());
Assert.assertTrue(savedRule.getCreatedTime() > 0);
Assert.assertEquals(savedTenant.getId(), savedRule.getTenantId());
}
@Test
public void testFindRuleById() throws Exception {
RuleMetaData rule = createRuleMetaData(tenantPlugin);
RuleMetaData savedRule = doPost("/api/rule", rule, RuleMetaData.class);
RuleMetaData foundRule = doGet("/api/rule/" + savedRule.getId().getId().toString(), RuleMetaData.class);
Assert.assertNotNull(foundRule);
Assert.assertEquals(savedRule, foundRule);
}
@Test
public void testFindRuleByPluginToken() throws Exception {
RuleMetaData rule = createRuleMetaData(tenantPlugin);
RuleMetaData savedRule = doPost("/api/rule", rule, RuleMetaData.class);
List<RuleMetaData> foundRules = doGetTyped("/api/rule/token/" + savedRule.getPluginToken(),
new TypeReference<List<RuleMetaData>>() {
});
Assert.assertNotNull(foundRules);
Assert.assertEquals(1, foundRules.size());
Assert.assertEquals(savedRule, foundRules.get(0));
}
@Test
public void testActivateRule() throws Exception {
RuleMetaData rule = createRuleMetaData(tenantPlugin);
RuleMetaData savedRule = doPost("/api/rule", rule, RuleMetaData.class);
doPost("/api/rule/" + savedRule.getId().getId().toString() + "/activate").andExpect(status().isBadRequest());
doPost("/api/plugin/" + tenantPlugin.getId().getId().toString() + "/activate").andExpect(status().isOk());
doPost("/api/rule/" + savedRule.getId().getId().toString() + "/activate").andExpect(status().isOk());
}
@Test
public void testSuspendRule() throws Exception {
RuleMetaData rule = createRuleMetaData(tenantPlugin);
RuleMetaData savedRule = doPost("/api/rule", rule, RuleMetaData.class);
doPost("/api/plugin/" + tenantPlugin.getId().getId().toString() + "/activate").andExpect(status().isOk());
doPost("/api/rule/" + savedRule.getId().getId().toString() + "/activate").andExpect(status().isOk());
doPost("/api/rule/" + savedRule.getId().getId().toString() + "/suspend").andExpect(status().isOk());
}
@Test
public void testFindSystemRules() throws Exception {
loginSysAdmin();
List<RuleMetaData> rules = testRulesCreation("/api/rule/system", sysPlugin);
for (RuleMetaData rule : rules) {
doDelete("/api/rule/" + rule.getId().getId()).andExpect(status().isOk());
}
loginTenantAdmin();
}
@Test
public void testFindCurrentTenantPlugins() throws Exception {
List<RuleMetaData> rules = testRulesCreation("/api/rule", tenantPlugin);
for (RuleMetaData rule : rules) {
doDelete("/api/rule/" + rule.getId().getId()).andExpect(status().isOk());
}
}
@Test
public void testFindTenantPlugins() throws Exception {
List<RuleMetaData> rules = testRulesCreation("/api/rule", tenantPlugin);
loginSysAdmin();
List<RuleMetaData> loadedRules = new ArrayList<>();
TextPageLink pageLink = new TextPageLink(3);
TextPageData<RuleMetaData> pageData;
do {
pageData = doGetTypedWithPageLink("/api/rule/tenant/" + savedTenant.getId().getId().toString() + "?",
new TypeReference<TextPageData<RuleMetaData>>() {
}, pageLink);
loadedRules.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink();
}
} while (pageData.hasNext());
Collections.sort(rules, idComparator);
Collections.sort(loadedRules, idComparator);
Assert.assertEquals(rules, loadedRules);
for (RuleMetaData rule : rules) {
doDelete("/api/rule/" + rule.getId().getId()).andExpect(status().isOk());
}
}
private List<RuleMetaData> testRulesCreation(String url, PluginMetaData plugin) throws Exception {
List<RuleMetaData> rules = new ArrayList<>();
for (int i = 0; i < 6; i++) {
RuleMetaData rule = createRuleMetaData(plugin);
rule.setPluginToken(plugin.getApiToken());
rule.setName(rule.getName() + i);
rules.add(doPost("/api/rule", rule, RuleMetaData.class));
}
List<RuleMetaData> loadedRules = new ArrayList<>();
TextPageLink pageLink = new TextPageLink(3);
TextPageData<RuleMetaData> pageData;
do {
pageData = doGetTypedWithPageLink(url + "?",
new TypeReference<TextPageData<RuleMetaData>>() {
}, pageLink);
loadedRules.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink();
}
} while (pageData.hasNext());
loadedRules = loadedRules.stream().filter(p -> !p.getName().equals("System Telemetry Rule")).collect(Collectors.toList());
Collections.sort(rules, idComparator);
Collections.sort(loadedRules, idComparator);
Assert.assertEquals(rules, loadedRules);
return loadedRules;
}
public static RuleMetaData createRuleMetaData(PluginMetaData plugin) throws IOException {
RuleMetaData rule = new RuleMetaData();
rule.setName("My Rule");
rule.setPluginToken(plugin.getApiToken());
rule.setFilters(mapper.readTree("[{\"clazz\":\"org.thingsboard.server.extensions.core.filter.MsgTypeFilter\", " +
"\"name\":\"TelemetryFilter\", " +
"\"configuration\": {\"messageTypes\":[\"POST_TELEMETRY\",\"POST_ATTRIBUTES\",\"GET_ATTRIBUTES\"]}}]"));
rule.setAction(mapper.readTree("{\"clazz\":\"org.thingsboard.server.extensions.core.action.telemetry.TelemetryPluginAction\", \"name\":\"TelemetryMsgConverterAction\", " +
"\"configuration\":{\"timeUnit\":\"DAYS\", \"ttlValue\":1}}"));
return rule;
}
}

26
application/src/test/java/org/thingsboard/server/controller/sql/PluginControllerSqlTest.java

@ -1,26 +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.controller.sql;
import org.thingsboard.server.controller.BasePluginControllerTest;
import org.thingsboard.server.dao.service.DaoSqlTest;
/**
* Created by Valerii Sosliuk on 6/28/2017.
*/
@DaoSqlTest
public class PluginControllerSqlTest extends BasePluginControllerTest {
}

1
application/src/test/java/org/thingsboard/server/mqtt/rpc/sql/MqttServerSideRpcSqlIntegrationTest.java

@ -15,7 +15,6 @@
*/
package org.thingsboard.server.mqtt.rpc.sql;
import org.thingsboard.server.dao.service.DaoNoSqlTest;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.mqtt.rpc.AbstractMqttServerSideRpcIntegrationTest;

42
application/src/test/java/org/thingsboard/server/rules/RuleEngineNoSqlTestSuite.java

@ -0,0 +1,42 @@
/**
* 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.rules;
import org.cassandraunit.dataset.cql.ClassPathCQLDataSet;
import org.junit.ClassRule;
import org.junit.extensions.cpsuite.ClasspathSuite;
import org.junit.runner.RunWith;
import org.thingsboard.server.dao.CustomCassandraCQLUnit;
import org.thingsboard.server.dao.CustomSqlUnit;
import java.util.Arrays;
@RunWith(ClasspathSuite.class)
@ClasspathSuite.ClassnameFilters({
"org.thingsboard.server.rules.flow.nosql.*Test",
"org.thingsboard.server.rules.lifecycle.nosql.*Test"
})
public class RuleEngineNoSqlTestSuite {
@ClassRule
public static CustomCassandraCQLUnit cassandraUnit =
new CustomCassandraCQLUnit(
Arrays.asList(
new ClassPathCQLDataSet("cassandra/schema.cql", false, false),
new ClassPathCQLDataSet("cassandra/system-data.cql", false, false)),
"cassandra-test.yaml", 30000l);
}

3
application/src/test/java/org/thingsboard/server/rules/RuleEngineSqlTestSuite.java

@ -24,7 +24,8 @@ import java.util.Arrays;
@RunWith(ClasspathSuite.class)
@ClasspathSuite.ClassnameFilters({
"org.thingsboard.server.rules.flow.*Test"})
"org.thingsboard.server.rules.flow.sql.*Test",
"org.thingsboard.server.rules.lifecycle.sql.*Test"})
public class RuleEngineSqlTestSuite {
@ClassRule

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

@ -16,6 +16,8 @@
package org.thingsboard.server.rules.flow;
import com.datastax.driver.core.utils.UUIDs;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.collect.Lists;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.junit.After;
@ -28,6 +30,7 @@ import org.thingsboard.server.actors.service.ActorService;
import org.thingsboard.server.common.data.*;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
@ -40,8 +43,10 @@ import org.thingsboard.server.controller.AbstractRuleEngineControllerTest;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.rule.RuleChainService;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -60,9 +65,6 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
@Autowired
protected AttributesService attributesService;
@Autowired
protected RuleChainService ruleChainService;
@Before
public void beforeTest() throws Exception {
loginSysAdmin();
@ -71,6 +73,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
ruleChainService.deleteRuleChainsByTenantId(savedTenant.getId());
tenantAdmin = new User();
tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
@ -149,7 +152,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
"CUSTOM",
device.getId(),
new TbMsgMetaData(),
"{}");
"{}", null, null, 0L);
actorService.onMsg(new ServiceToRuleEngineMsg(savedTenant.getId(), tbMsg));
Thread.sleep(3000);
@ -166,7 +169,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
Assert.assertEquals(ruleChain.getFirstRuleNodeId(), outEvent.getEntityId());
Assert.assertEquals(device.getId().getId().toString(), outEvent.getBody().get("entityId").asText());
Assert.assertEquals("serverAttributeValue1", outEvent.getBody().get("metadata").get("ss.serverAttributeKey1").asText());
Assert.assertEquals("serverAttributeValue1", getMetadata(outEvent).get("ss_serverAttributeKey1").asText());
RuleChain finalRuleChain = ruleChain;
RuleNode lastRuleNode = metaData.getNodes().stream().filter(node -> !node.getId().equals(finalRuleChain.getFirstRuleNodeId())).findFirst().get();
@ -183,8 +186,131 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
Assert.assertEquals(lastRuleNode.getId(), outEvent.getEntityId());
Assert.assertEquals(device.getId().getId().toString(), outEvent.getBody().get("entityId").asText());
Assert.assertEquals("serverAttributeValue1", outEvent.getBody().get("metadata").get("ss.serverAttributeKey1").asText());
Assert.assertEquals("serverAttributeValue2", outEvent.getBody().get("metadata").get("ss.serverAttributeKey2").asText());
Assert.assertEquals("serverAttributeValue1", getMetadata(outEvent).get("ss_serverAttributeKey1").asText());
Assert.assertEquals("serverAttributeValue2", getMetadata(outEvent).get("ss_serverAttributeKey2").asText());
List<TbMsg> unAckMsgList = Lists.newArrayList(msgQueue.findUnprocessed(ruleChain.getId().getId(), 0L));
Assert.assertEquals(0, unAckMsgList.size());
}
@Test
public void testTwoRuleChainsWithTwoRules() throws Exception {
// Creating Rule Chain
RuleChain rootRuleChain = new RuleChain();
rootRuleChain.setName("Root Rule Chain");
rootRuleChain.setTenantId(savedTenant.getId());
rootRuleChain.setRoot(true);
rootRuleChain.setDebugMode(true);
rootRuleChain = saveRuleChain(rootRuleChain);
Assert.assertNull(rootRuleChain.getFirstRuleNodeId());
// Creating Rule Chain
RuleChain secondaryRuleChain = new RuleChain();
secondaryRuleChain.setName("Secondary Rule Chain");
secondaryRuleChain.setTenantId(savedTenant.getId());
secondaryRuleChain.setRoot(false);
secondaryRuleChain.setDebugMode(true);
secondaryRuleChain = saveRuleChain(secondaryRuleChain);
Assert.assertNull(secondaryRuleChain.getFirstRuleNodeId());
RuleChainMetaData rootMetaData = new RuleChainMetaData();
rootMetaData.setRuleChainId(rootRuleChain.getId());
RuleNode ruleNode1 = new RuleNode();
ruleNode1.setName("Simple Rule Node 1");
ruleNode1.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName());
ruleNode1.setDebugMode(true);
TbGetAttributesNodeConfiguration configuration1 = new TbGetAttributesNodeConfiguration();
configuration1.setServerAttributeNames(Collections.singletonList("serverAttributeKey1"));
ruleNode1.setConfiguration(mapper.valueToTree(configuration1));
rootMetaData.setNodes(Collections.singletonList(ruleNode1));
rootMetaData.setFirstNodeIndex(0);
rootMetaData.addRuleChainConnectionInfo(0, secondaryRuleChain.getId(), "Success", mapper.createObjectNode());
rootMetaData = saveRuleChainMetaData(rootMetaData);
Assert.assertNotNull(rootMetaData);
rootRuleChain = getRuleChain(rootRuleChain.getId());
Assert.assertNotNull(rootRuleChain.getFirstRuleNodeId());
RuleChainMetaData secondaryMetaData = new RuleChainMetaData();
secondaryMetaData.setRuleChainId(secondaryRuleChain.getId());
RuleNode ruleNode2 = new RuleNode();
ruleNode2.setName("Simple Rule Node 2");
ruleNode2.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName());
ruleNode2.setDebugMode(true);
TbGetAttributesNodeConfiguration configuration2 = new TbGetAttributesNodeConfiguration();
configuration2.setServerAttributeNames(Collections.singletonList("serverAttributeKey2"));
ruleNode2.setConfiguration(mapper.valueToTree(configuration2));
secondaryMetaData.setNodes(Collections.singletonList(ruleNode2));
secondaryMetaData.setFirstNodeIndex(0);
secondaryMetaData = saveRuleChainMetaData(secondaryMetaData);
Assert.assertNotNull(secondaryMetaData);
// Saving the device
Device device = new Device();
device.setName("My device");
device.setType("default");
device = doPost("/api/device", device, Device.class);
attributesService.save(device.getId(), DataConstants.SERVER_SCOPE,
Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry("serverAttributeKey1", "serverAttributeValue1"), System.currentTimeMillis())));
attributesService.save(device.getId(), DataConstants.SERVER_SCOPE,
Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry("serverAttributeKey2", "serverAttributeValue2"), System.currentTimeMillis())));
Thread.sleep(1000);
// Pushing Message to the system
TbMsg tbMsg = new TbMsg(UUIDs.timeBased(),
"CUSTOM",
device.getId(),
new TbMsgMetaData(),
"{}", null, null, 0L);
actorService.onMsg(new ServiceToRuleEngineMsg(savedTenant.getId(), tbMsg));
Thread.sleep(3000);
TimePageData<Event> events = getDebugEvents(savedTenant.getId(), rootRuleChain.getFirstRuleNodeId(), 1000);
Assert.assertEquals(2, events.getData().size());
Event inEvent = events.getData().stream().filter(e -> e.getBody().get("type").asText().equals(DataConstants.IN)).findFirst().get();
Assert.assertEquals(rootRuleChain.getFirstRuleNodeId(), inEvent.getEntityId());
Assert.assertEquals(device.getId().getId().toString(), inEvent.getBody().get("entityId").asText());
Event outEvent = events.getData().stream().filter(e -> e.getBody().get("type").asText().equals(DataConstants.OUT)).findFirst().get();
Assert.assertEquals(rootRuleChain.getFirstRuleNodeId(), outEvent.getEntityId());
Assert.assertEquals(device.getId().getId().toString(), outEvent.getBody().get("entityId").asText());
Assert.assertEquals("serverAttributeValue1", getMetadata(outEvent).get("ss_serverAttributeKey1").asText());
RuleChain finalRuleChain = rootRuleChain;
RuleNode lastRuleNode = secondaryMetaData.getNodes().stream().filter(node -> !node.getId().equals(finalRuleChain.getFirstRuleNodeId())).findFirst().get();
events = getDebugEvents(savedTenant.getId(), lastRuleNode.getId(), 1000);
Assert.assertEquals(2, events.getData().size());
inEvent = events.getData().stream().filter(e -> e.getBody().get("type").asText().equals(DataConstants.IN)).findFirst().get();
Assert.assertEquals(lastRuleNode.getId(), inEvent.getEntityId());
Assert.assertEquals(device.getId().getId().toString(), inEvent.getBody().get("entityId").asText());
outEvent = events.getData().stream().filter(e -> e.getBody().get("type").asText().equals(DataConstants.OUT)).findFirst().get();
Assert.assertEquals(lastRuleNode.getId(), outEvent.getEntityId());
Assert.assertEquals(device.getId().getId().toString(), outEvent.getBody().get("entityId").asText());
Assert.assertEquals("serverAttributeValue1", getMetadata(outEvent).get("ss_serverAttributeKey1").asText());
Assert.assertEquals("serverAttributeValue2", getMetadata(outEvent).get("ss_serverAttributeKey2").asText());
List<TbMsg> unAckMsgList = Lists.newArrayList(msgQueue.findUnprocessed(rootRuleChain.getId().getId(), 0L));
Assert.assertEquals(0, unAckMsgList.size());
unAckMsgList = Lists.newArrayList(msgQueue.findUnprocessed(secondaryRuleChain.getId().getId(), 0L));
Assert.assertEquals(0, unAckMsgList.size());
}
}

8
application/src/test/java/org/thingsboard/server/controller/nosql/PluginControllerNoSqlTest.java → application/src/test/java/org/thingsboard/server/rules/flow/nosql/RuleEngineFlowNoSqlIntegrationTest.java

@ -13,14 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.controller.nosql;
package org.thingsboard.server.rules.flow.nosql;
import org.thingsboard.server.controller.BasePluginControllerTest;
import org.thingsboard.server.dao.service.DaoNoSqlTest;
import org.thingsboard.server.rules.flow.AbstractRuleEngineFlowIntegrationTest;
/**
* Created by Valerii Sosliuk on 6/28/2017.
* Created by Valerii Sosliuk on 8/22/2017.
*/
@DaoNoSqlTest
public class PluginControllerNoSqlTest extends BasePluginControllerTest {
public class RuleEngineFlowNoSqlIntegrationTest extends AbstractRuleEngineFlowIntegrationTest {
}

3
application/src/test/java/org/thingsboard/server/rules/flow/RuleEngineFlowSqlIntegrationTest.java → application/src/test/java/org/thingsboard/server/rules/flow/sql/RuleEngineFlowSqlIntegrationTest.java

@ -13,10 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.rules.flow;
package org.thingsboard.server.rules.flow.sql;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.mqtt.rpc.AbstractMqttServerSideRpcIntegrationTest;
import org.thingsboard.server.rules.flow.AbstractRuleEngineFlowIntegrationTest;
/**
* Created by Valerii Sosliuk on 8/22/2017.

76
application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java

@ -16,6 +16,7 @@
package org.thingsboard.server.rules.lifecycle;
import com.datastax.driver.core.utils.UUIDs;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Assert;
@ -42,6 +43,7 @@ import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
import org.thingsboard.server.controller.AbstractRuleEngineControllerTest;
import org.thingsboard.server.dao.attributes.AttributesService;
import java.io.IOException;
import java.util.Collections;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -69,6 +71,7 @@ public abstract class AbstractRuleEngineLifecycleIntegrationTest extends Abstrac
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
ruleChainService.deleteRuleChainsByTenantId(savedTenant.getId());
tenantAdmin = new User();
tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
@ -135,7 +138,8 @@ public abstract class AbstractRuleEngineLifecycleIntegrationTest extends Abstrac
"CUSTOM",
device.getId(),
new TbMsgMetaData(),
"{}");
"{}",
null, null, 0L);
actorService.onMsg(new ServiceToRuleEngineMsg(savedTenant.getId(), tbMsg));
Thread.sleep(3000);
@ -152,7 +156,75 @@ public abstract class AbstractRuleEngineLifecycleIntegrationTest extends Abstrac
Assert.assertEquals(ruleChain.getFirstRuleNodeId(), outEvent.getEntityId());
Assert.assertEquals(device.getId().getId().toString(), outEvent.getBody().get("entityId").asText());
Assert.assertEquals("serverAttributeValue", outEvent.getBody().get("metadata").get("ss.serverAttributeKey").asText());
Assert.assertEquals("serverAttributeValue", getMetadata(outEvent).get("ss_serverAttributeKey").asText());
}
@Test
public void testRuleChainWithOneRuleAndMsgFromQueue() throws Exception {
// Creating Rule Chain
RuleChain ruleChain = new RuleChain();
ruleChain.setName("Simple Rule Chain");
ruleChain.setTenantId(savedTenant.getId());
ruleChain.setRoot(true);
ruleChain.setDebugMode(true);
ruleChain = saveRuleChain(ruleChain);
Assert.assertNull(ruleChain.getFirstRuleNodeId());
// Saving the device
Device device = new Device();
device.setName("My device");
device.setType("default");
device = doPost("/api/device", device, Device.class);
attributesService.save(device.getId(), DataConstants.SERVER_SCOPE,
Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry("serverAttributeKey", "serverAttributeValue"), System.currentTimeMillis())));
// Pushing Message to the system
TbMsg tbMsg = new TbMsg(UUIDs.timeBased(),
"CUSTOM",
device.getId(),
new TbMsgMetaData(),
"{}",
ruleChain.getId(), null, 0L);
msgQueue.put(tbMsg, ruleChain.getId().getId(), 0L);
Thread.sleep(1000);
RuleChainMetaData metaData = new RuleChainMetaData();
metaData.setRuleChainId(ruleChain.getId());
RuleNode ruleNode = new RuleNode();
ruleNode.setName("Simple Rule Node");
ruleNode.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName());
ruleNode.setDebugMode(true);
TbGetAttributesNodeConfiguration configuration = new TbGetAttributesNodeConfiguration();
configuration.setServerAttributeNames(Collections.singletonList("serverAttributeKey"));
ruleNode.setConfiguration(mapper.valueToTree(configuration));
metaData.setNodes(Collections.singletonList(ruleNode));
metaData.setFirstNodeIndex(0);
metaData = saveRuleChainMetaData(metaData);
Assert.assertNotNull(metaData);
ruleChain = getRuleChain(ruleChain.getId());
Assert.assertNotNull(ruleChain.getFirstRuleNodeId());
Thread.sleep(3000);
TimePageData<Event> events = getDebugEvents(savedTenant.getId(), ruleChain.getFirstRuleNodeId(), 1000);
Assert.assertEquals(2, events.getData().size());
Event inEvent = events.getData().stream().filter(e -> e.getBody().get("type").asText().equals(DataConstants.IN)).findFirst().get();
Assert.assertEquals(ruleChain.getFirstRuleNodeId(), inEvent.getEntityId());
Assert.assertEquals(device.getId().getId().toString(), inEvent.getBody().get("entityId").asText());
Event outEvent = events.getData().stream().filter(e -> e.getBody().get("type").asText().equals(DataConstants.OUT)).findFirst().get();
Assert.assertEquals(ruleChain.getFirstRuleNodeId(), outEvent.getEntityId());
Assert.assertEquals(device.getId().getId().toString(), outEvent.getBody().get("entityId").asText());
Assert.assertEquals("serverAttributeValue", getMetadata(outEvent).get("ss_serverAttributeKey").asText());
}
}

8
application/src/test/java/org/thingsboard/server/controller/nosql/RuleControllerNoSqlTest.java → application/src/test/java/org/thingsboard/server/rules/lifecycle/nosql/RuleEngineLifecycleNoSqlIntegrationTest.java

@ -13,14 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.controller.nosql;
package org.thingsboard.server.rules.lifecycle.nosql;
import org.thingsboard.server.controller.BaseRuleControllerTest;
import org.thingsboard.server.dao.service.DaoNoSqlTest;
import org.thingsboard.server.rules.lifecycle.AbstractRuleEngineLifecycleIntegrationTest;
/**
* Created by Valerii Sosliuk on 6/28/2017.
* Created by Valerii Sosliuk on 8/22/2017.
*/
@DaoNoSqlTest
public class RuleControllerNoSqlTest extends BaseRuleControllerTest {
public class RuleEngineLifecycleNoSqlIntegrationTest extends AbstractRuleEngineLifecycleIntegrationTest {
}

3
application/src/test/java/org/thingsboard/server/rules/lifecycle/RuleEngineLifecycleSqlIntegrationTest.java → application/src/test/java/org/thingsboard/server/rules/lifecycle/sql/RuleEngineLifecycleSqlIntegrationTest.java

@ -13,10 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.rules.lifecycle;
package org.thingsboard.server.rules.lifecycle.sql;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.rules.flow.AbstractRuleEngineFlowIntegrationTest;
import org.thingsboard.server.rules.lifecycle.AbstractRuleEngineLifecycleIntegrationTest;
/**
* Created by Valerii Sosliuk on 8/22/2017.

14
application/src/test/java/org/thingsboard/server/service/script/NashornJsEngineTest.java

@ -42,7 +42,7 @@ public class NashornJsEngineTest {
metaData.putValue("humidity", "99");
String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}";
TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson);
TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson, null, null, 0L);
TbMsg actual = scriptEngine.executeUpdate(msg);
assertEquals("70", actual.getMetaData().getValue("temp"));
@ -57,7 +57,7 @@ public class NashornJsEngineTest {
metaData.putValue("humidity", "99");
String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}";
TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson);
TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson, null, null, 0L);
TbMsg actual = scriptEngine.executeUpdate(msg);
assertEquals("94", actual.getMetaData().getValue("newAttr"));
@ -72,7 +72,7 @@ public class NashornJsEngineTest {
metaData.putValue("humidity", "99");
String rawJson = "{\"name\":\"Vit\",\"passed\": 5,\"bigObj\":{\"prop\":42}}";
TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson);
TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson, null, null, 0L);
TbMsg actual = scriptEngine.executeUpdate(msg);
@ -89,7 +89,7 @@ public class NashornJsEngineTest {
metaData.putValue("humidity", "99");
String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}";
TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson);
TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson, null, null, 0L);
assertFalse(scriptEngine.executeFilter(msg));
}
@ -102,7 +102,7 @@ public class NashornJsEngineTest {
metaData.putValue("humidity", "99");
String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}";
TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson);
TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson, null, null, 0L);
assertTrue(scriptEngine.executeFilter(msg));
}
@ -122,7 +122,7 @@ public class NashornJsEngineTest {
metaData.putValue("humidity", "99");
String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}";
TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson);
TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson, null, null, 0L);
Set<String> actual = scriptEngine.executeSwitch(msg);
assertEquals(Sets.newHashSet("one"), actual);
}
@ -143,7 +143,7 @@ public class NashornJsEngineTest {
metaData.putValue("humidity", "99");
String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}";
TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson);
TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson, null, null, 0L);
Set<String> actual = scriptEngine.executeSwitch(msg);
assertEquals(Sets.newHashSet("one", "three"), actual);
}

1
application/src/test/java/org/thingsboard/server/system/SystemSqlTestSuite.java

@ -35,5 +35,4 @@ public class SystemSqlTestSuite {
"sql/drop-all-tables.sql",
"sql-test.properties");
}

5
common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java

@ -45,4 +45,9 @@ public class DataConstants {
public static final String IN = "IN";
public static final String OUT = "OUT";
public static final String INACTIVITY_EVENT = "INACTIVITY_EVENT";
public static final String CONNECT_EVENT = "CONNECT_EVENT";
public static final String DISCONNECT_EVENT = "DISCONNECT_EVENT";
public static final String ACTIVITY_EVENT = "ACTIVITY_EVENT";
}

2
extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/cmd/RpcRequest.java → common/data/src/main/java/org/thingsboard/server/common/data/rpc/RpcRequest.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.extensions.core.plugin.rpc.cmd;
package org.thingsboard.server.common.data.rpc;
import lombok.Data;

2
extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/ToDeviceRpcRequestBody.java → common/data/src/main/java/org/thingsboard/server/common/data/rpc/ToDeviceRpcRequestBody.java

@ -13,7 +13,7 @@
* 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.common.data.rpc;
import lombok.Data;

4
common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleNode.java

@ -22,6 +22,7 @@ import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId;
@ -32,6 +33,7 @@ public class RuleNode extends SearchTextBasedWithAdditionalInfo<RuleNodeId> impl
private static final long serialVersionUID = -5656679015121235465L;
private RuleChainId ruleChainId;
private String type;
private String name;
private boolean debugMode;
@ -49,8 +51,10 @@ public class RuleNode extends SearchTextBasedWithAdditionalInfo<RuleNodeId> impl
public RuleNode(RuleNode ruleNode) {
super(ruleNode);
this.ruleChainId = ruleNode.getRuleChainId();
this.type = ruleNode.getType();
this.name = ruleNode.getName();
this.debugMode = ruleNode.isDebugMode();
this.setConfiguration(ruleNode.getConfiguration());
}

53
common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java

@ -18,32 +18,40 @@ package org.thingsboard.server.common.msg;
/**
* Created by ashvayka on 15.03.18.
*/
//TODO: add all "See" references
public enum MsgType {
/**
* ADDED/UPDATED/DELETED events for server nodes.
*
* See {@link org.thingsboard.server.common.msg.cluster.ClusterEventMsg}
*/
CLUSTER_EVENT_MSG,
/**
* ADDED/UPDATED/DELETED events for main entities.
*
* @See {@link org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg}
* See {@link org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg}
*/
COMPONENT_LIFE_CYCLE_MSG,
/**
* Misc messages from the REST API/SERVICE layer to the new rule engine.
*
* @See {@link org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg}
* See {@link org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg}
*/
SERVICE_TO_RULE_ENGINE_MSG,
SESSION_TO_DEVICE_ACTOR_MSG,
DEVICE_ACTOR_TO_SESSION_MSG,
/**
* Message that is sent by RuleChainActor to RuleActor with command to process TbMsg.
*/
RULE_CHAIN_TO_RULE_MSG,
/**
* Message that is sent by RuleChainActor to other RuleChainActor with command to process TbMsg.
*/
RULE_CHAIN_TO_RULE_CHAIN_MSG,
/**
* Message that is sent by RuleActor to RuleChainActor with command to process TbMsg by next nodes in chain.
*/
@ -59,4 +67,35 @@ public enum MsgType {
*/
RULE_TO_SELF_MSG,
/**
* Message that is sent by Session Actor to Device Actor. Represents messages from the device itself.
*/
DEVICE_SESSION_TO_DEVICE_ACTOR_MSG,
DEVICE_ATTRIBUTES_UPDATE_TO_DEVICE_ACTOR_MSG,
DEVICE_CREDENTIALS_UPDATE_TO_DEVICE_ACTOR_MSG,
DEVICE_NAME_OR_TYPE_UPDATE_TO_DEVICE_ACTOR_MSG,
DEVICE_RPC_REQUEST_TO_DEVICE_ACTOR_MSG,
SERVER_RPC_RESPONSE_TO_DEVICE_ACTOR_MSG,
DEVICE_ACTOR_SERVER_SIDE_RPC_TIMEOUT_MSG,
DEVICE_ACTOR_CLIENT_SIDE_RPC_TIMEOUT_MSG,
DEVICE_ACTOR_QUEUE_TIMEOUT_MSG,
/**
* Message that is sent from the Device Actor to Rule Engine. Requires acknowledgement
*/
DEVICE_ACTOR_TO_RULE_ENGINE_MSG,
/**
* Message that is sent from Rule Engine to the Device Actor when message is successfully pushed to queue.
*/
RULE_ENGINE_QUEUE_PUT_ACK_MSG;
}

45
common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java

@ -15,12 +15,13 @@
*/
package org.thingsboard.server.common.msg;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.msg.gen.MsgProtos;
import java.io.Serializable;
@ -41,22 +42,40 @@ public final class TbMsg implements Serializable {
private final TbMsgDataType dataType;
private final String data;
public TbMsg(UUID id, String type, EntityId originator, TbMsgMetaData metaData, String data) {
//The following fields are not persisted to DB, because they can always be recovered from the context;
private final RuleChainId ruleChainId;
private final RuleNodeId ruleNodeId;
private final long clusterPartition;
public TbMsg(UUID id, String type, EntityId originator, TbMsgMetaData metaData, String data,
RuleChainId ruleChainId, RuleNodeId ruleNodeId, long clusterPartition) {
this.id = id;
this.type = type;
this.originator = originator;
this.metaData = metaData;
this.dataType = TbMsgDataType.JSON;
this.data = data;
this.dataType = TbMsgDataType.JSON;
this.ruleChainId = ruleChainId;
this.ruleNodeId = ruleNodeId;
this.clusterPartition = clusterPartition;
}
public static ByteBuffer toBytes(TbMsg msg) {
MsgProtos.TbMsgProto.Builder builder = MsgProtos.TbMsgProto.newBuilder();
builder.setId(msg.getId().toString());
builder.setType(msg.getType());
if (msg.getOriginator() != null) {
builder.setEntityType(msg.getOriginator().getEntityType().name());
builder.setEntityId(msg.getOriginator().getId().toString());
builder.setEntityType(msg.getOriginator().getEntityType().name());
builder.setEntityIdMSB(msg.getOriginator().getId().getMostSignificantBits());
builder.setEntityIdLSB(msg.getOriginator().getId().getLeastSignificantBits());
if (msg.getRuleChainId() != null) {
builder.setRuleChainIdMSB(msg.getRuleChainId().getId().getMostSignificantBits());
builder.setRuleChainIdLSB(msg.getRuleChainId().getId().getLeastSignificantBits());
}
if (msg.getRuleNodeId() != null) {
builder.setRuleNodeIdMSB(msg.getRuleNodeId().getId().getMostSignificantBits());
builder.setRuleNodeIdLSB(msg.getRuleNodeId().getId().getLeastSignificantBits());
}
if (msg.getMetaData() != null) {
@ -73,15 +92,21 @@ public final class TbMsg implements Serializable {
try {
MsgProtos.TbMsgProto proto = MsgProtos.TbMsgProto.parseFrom(buffer.array());
TbMsgMetaData metaData = new TbMsgMetaData(proto.getMetaData().getDataMap());
EntityId entityId = EntityIdFactory.getByTypeAndId(proto.getEntityType(), proto.getEntityId());
EntityId entityId = EntityIdFactory.getByTypeAndUuid(proto.getEntityType(), new UUID(proto.getEntityIdMSB(), proto.getEntityIdLSB()));
RuleChainId ruleChainId = new RuleChainId(new UUID(proto.getRuleChainIdMSB(), proto.getRuleChainIdLSB()));
RuleNodeId ruleNodeId = null;
if(proto.getRuleNodeIdMSB() != 0L && proto.getRuleNodeIdLSB() != 0L) {
ruleNodeId = new RuleNodeId(new UUID(proto.getRuleNodeIdMSB(), proto.getRuleNodeIdLSB()));
}
TbMsgDataType dataType = TbMsgDataType.values()[proto.getDataType()];
return new TbMsg(UUID.fromString(proto.getId()), proto.getType(), entityId, metaData, dataType, proto.getData());
return new TbMsg(UUID.fromString(proto.getId()), proto.getType(), entityId, metaData, dataType, proto.getData(), ruleChainId, ruleNodeId, proto.getClusterPartition());
} catch (InvalidProtocolBufferException e) {
throw new IllegalStateException("Could not parse protobuf for TbMsg", e);
}
}
public TbMsg copy() {
return new TbMsg(id, type, originator, metaData.copy(), dataType, data);
public TbMsg copy(UUID newId, RuleChainId ruleChainId, RuleNodeId ruleNodeId, long clusterPartition) {
return new TbMsg(newId, type, originator, metaData, dataType, data, ruleChainId, ruleNodeId, clusterPartition);
}
}

4
common/message/src/main/java/org/thingsboard/server/common/msg/TbMsgMetaData.java

@ -31,10 +31,10 @@ import java.util.concurrent.ConcurrentHashMap;
@NoArgsConstructor
public final class TbMsgMetaData implements Serializable {
private Map<String, String> data = new ConcurrentHashMap<>();
private final Map<String, String> data = new ConcurrentHashMap<>();
public TbMsgMetaData(Map<String, String> data) {
this.data = data;
this.data.putAll(data);
}
public String getValue(String key) {

8
common/message/src/main/java/org/thingsboard/server/common/msg/cluster/ClusterEventMsg.java

@ -16,14 +16,20 @@
package org.thingsboard.server.common.msg.cluster;
import lombok.Data;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.TbActorMsg;
/**
* @author Andrew Shvayka
*/
@Data
public final class ClusterEventMsg {
public final class ClusterEventMsg implements TbActorMsg {
private final ServerAddress serverAddress;
private final boolean added;
@Override
public MsgType getMsgType() {
return MsgType.CLUSTER_EVENT_MSG;
}
}

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

Loading…
Cancel
Save