diff --git a/application/src/main/data/json/demo/dashboards/firmware.json b/application/src/main/data/json/demo/dashboards/firmware.json
index cfd0ed68c4..779f2cb93c 100644
--- a/application/src/main/data/json/demo/dashboards/firmware.json
+++ b/application/src/main/data/json/demo/dashboards/firmware.json
@@ -33,15 +33,15 @@
"enableSearch": true,
"displayPagination": true,
"defaultPageSize": 10,
- "defaultSortOrder": "entityLabel",
- "displayEntityName": false,
+ "defaultSortOrder": "entityName",
+ "displayEntityName": true,
"displayEntityType": false,
"enableSelectColumnDisplay": false,
"enableStickyHeader": true,
"enableStickyAction": false,
"entitiesTitle": "Devices",
- "displayEntityLabel": true,
- "entityLabelColumnTitle": "Device"
+ "displayEntityLabel": false,
+ "entityNameColumnTitle": "Device"
},
"title": "New Entities table",
"dropShadow": true,
@@ -828,15 +828,15 @@
"enableSearch": true,
"displayPagination": true,
"defaultPageSize": 10,
- "defaultSortOrder": "entityLabel",
- "displayEntityName": false,
+ "defaultSortOrder": "entityName",
+ "displayEntityName": true,
"displayEntityType": false,
"enableSelectColumnDisplay": false,
"enableStickyHeader": true,
"enableStickyAction": true,
"entitiesTitle": "Devices",
- "displayEntityLabel": true,
- "entityLabelColumnTitle": "Device"
+ "displayEntityLabel": false,
+ "entityNameColumnTitle": "Device"
},
"title": "New Entities table",
"dropShadow": true,
@@ -1125,15 +1125,15 @@
"enableSearch": true,
"displayPagination": true,
"defaultPageSize": 10,
- "defaultSortOrder": "entityLabel",
- "displayEntityName": false,
+ "defaultSortOrder": "entityName",
+ "displayEntityName": true,
"displayEntityType": false,
"enableSelectColumnDisplay": false,
"enableStickyHeader": true,
"enableStickyAction": true,
"entitiesTitle": "Devices",
- "displayEntityLabel": true,
- "entityLabelColumnTitle": "Device"
+ "displayEntityLabel": false,
+ "entityNameColumnTitle": "Device"
},
"title": "New Entities table",
"dropShadow": true,
@@ -1422,15 +1422,15 @@
"enableSearch": true,
"displayPagination": true,
"defaultPageSize": 10,
- "defaultSortOrder": "entityLabel",
- "displayEntityName": false,
+ "defaultSortOrder": "entityName",
+ "displayEntityName": true,
"displayEntityType": false,
"enableSelectColumnDisplay": false,
"enableStickyHeader": true,
"enableStickyAction": true,
"entitiesTitle": "Devices",
- "displayEntityLabel": true,
- "entityLabelColumnTitle": "Device"
+ "displayEntityLabel": false,
+ "entityNameColumnTitle": "Device"
},
"title": "New Entities table",
"dropShadow": true,
@@ -1719,15 +1719,15 @@
"enableSearch": true,
"displayPagination": true,
"defaultPageSize": 10,
- "defaultSortOrder": "entityLabel",
- "displayEntityName": false,
+ "defaultSortOrder": "entityName",
+ "displayEntityName": true,
"displayEntityType": false,
"enableSelectColumnDisplay": false,
"enableStickyHeader": true,
"enableStickyAction": true,
"entitiesTitle": "Devices",
- "displayEntityLabel": true,
- "entityLabelColumnTitle": "Device"
+ "displayEntityLabel": false,
+ "entityNameColumnTitle": "Device"
},
"title": "New Entities table",
"dropShadow": true,
diff --git a/application/src/main/data/json/demo/dashboards/software.json b/application/src/main/data/json/demo/dashboards/software.json
index 84316fa5fd..e9d7e9bbbd 100644
--- a/application/src/main/data/json/demo/dashboards/software.json
+++ b/application/src/main/data/json/demo/dashboards/software.json
@@ -33,15 +33,15 @@
"enableSearch": true,
"displayPagination": true,
"defaultPageSize": 10,
- "defaultSortOrder": "entityLabel",
- "displayEntityName": false,
+ "defaultSortOrder": "entityName",
+ "displayEntityName": true,
"displayEntityType": false,
"enableSelectColumnDisplay": false,
"enableStickyHeader": true,
"enableStickyAction": false,
"entitiesTitle": "Devices",
- "displayEntityLabel": true,
- "entityLabelColumnTitle": "Device"
+ "displayEntityLabel": false,
+ "entityNameColumnTitle": "Device"
},
"title": "New Entities table",
"dropShadow": true,
@@ -828,15 +828,15 @@
"enableSearch": true,
"displayPagination": true,
"defaultPageSize": 10,
- "defaultSortOrder": "entityLabel",
- "displayEntityName": false,
+ "defaultSortOrder": "entityName",
+ "displayEntityName": true,
"displayEntityType": false,
"enableSelectColumnDisplay": false,
"enableStickyHeader": true,
"enableStickyAction": true,
"entitiesTitle": "Devices",
- "displayEntityLabel": true,
- "entityLabelColumnTitle": "Device"
+ "displayEntityLabel": false,
+ "entityNameColumnTitle": "Device"
},
"title": "New Entities table",
"dropShadow": true,
@@ -1125,15 +1125,15 @@
"enableSearch": true,
"displayPagination": true,
"defaultPageSize": 10,
- "defaultSortOrder": "entityLabel",
- "displayEntityName": false,
+ "defaultSortOrder": "entityName",
+ "displayEntityName": true,
"displayEntityType": false,
"enableSelectColumnDisplay": false,
"enableStickyHeader": true,
"enableStickyAction": true,
"entitiesTitle": "Devices",
- "displayEntityLabel": true,
- "entityLabelColumnTitle": "Device"
+ "displayEntityLabel": false,
+ "entityNameColumnTitle": "Device"
},
"title": "New Entities table",
"dropShadow": true,
@@ -1422,15 +1422,15 @@
"enableSearch": true,
"displayPagination": true,
"defaultPageSize": 10,
- "defaultSortOrder": "entityLabel",
- "displayEntityName": false,
+ "defaultSortOrder": "entityName",
+ "displayEntityName": true,
"displayEntityType": false,
"enableSelectColumnDisplay": false,
"enableStickyHeader": true,
"enableStickyAction": true,
"entitiesTitle": "Devices",
- "displayEntityLabel": true,
- "entityLabelColumnTitle": "Device"
+ "displayEntityLabel": false,
+ "entityNameColumnTitle": "Device"
},
"title": "New Entities table",
"dropShadow": true,
@@ -1719,15 +1719,15 @@
"enableSearch": true,
"displayPagination": true,
"defaultPageSize": 10,
- "defaultSortOrder": "entityLabel",
- "displayEntityName": false,
+ "defaultSortOrder": "entityName",
+ "displayEntityName": true,
"displayEntityType": false,
"enableSelectColumnDisplay": false,
"enableStickyHeader": true,
"enableStickyAction": true,
"entitiesTitle": "Devices",
- "displayEntityLabel": true,
- "entityLabelColumnTitle": "Device"
+ "displayEntityLabel": false,
+ "entityNameColumnTitle": "Device"
},
"title": "New Entities table",
"dropShadow": true,
diff --git a/application/src/main/data/json/demo/edge_management/rule_chains/edge_root_rule_chain.json b/application/src/main/data/json/demo/edge_management/rule_chains/edge_root_rule_chain.json
deleted file mode 100644
index 740d64627e..0000000000
--- a/application/src/main/data/json/demo/edge_management/rule_chains/edge_root_rule_chain.json
+++ /dev/null
@@ -1,162 +0,0 @@
-{
- "ruleChain": {
- "additionalInfo": null,
- "name": "Edge Root Rule Chain",
- "type": "EDGE",
- "firstRuleNodeId": null,
- "root": true,
- "debugMode": false,
- "configuration": null
- },
- "metadata": {
- "firstNodeIndex": 0,
- "nodes": [
- {
- "additionalInfo": {
- "description": "Process incoming messages from devices with the alarm rules defined in the device profile. Dispatch all incoming messages with \"Success\" relation type.",
- "layoutX": 203,
- "layoutY": 259
- },
- "type": "org.thingsboard.rule.engine.profile.TbDeviceProfileNode",
- "name": "Device Profile Node",
- "debugMode": false,
- "configuration": {
- "persistAlarmRulesState": false,
- "fetchAlarmRulesStateOnStart": false
- }
- },
- {
- "additionalInfo": {
- "layoutX": 823,
- "layoutY": 157
- },
- "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode",
- "name": "Save Timeseries",
- "debugMode": false,
- "configuration": {
- "defaultTTL": 0
- }
- },
- {
- "additionalInfo": {
- "layoutX": 824,
- "layoutY": 52
- },
- "type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode",
- "name": "Save Client Attributes",
- "debugMode": false,
- "configuration": {
- "scope": "CLIENT_SCOPE"
- }
- },
- {
- "additionalInfo": {
- "layoutX": 347,
- "layoutY": 149
- },
- "type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode",
- "name": "Message Type Switch",
- "debugMode": false,
- "configuration": {
- "version": 0
- }
- },
- {
- "additionalInfo": {
- "layoutX": 825,
- "layoutY": 266
- },
- "type": "org.thingsboard.rule.engine.action.TbLogNode",
- "name": "Log RPC from Device",
- "debugMode": false,
- "configuration": {
- "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
- }
- },
- {
- "additionalInfo": {
- "layoutX": 824,
- "layoutY": 378
- },
- "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);"
- }
- },
- {
- "additionalInfo": {
- "layoutX": 824,
- "layoutY": 466
- },
- "type": "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode",
- "name": "RPC Call Request",
- "debugMode": false,
- "configuration": {
- "timeoutInSeconds": 60
- }
- },
- {
- "additionalInfo": {
- "layoutX": 1134,
- "layoutY": 132
- },
- "type": "org.thingsboard.rule.engine.edge.TbMsgPushToCloudNode",
- "name": "Push to cloud",
- "debugMode": false,
- "configuration": {
- "version": 0
- }
- }
- ],
- "connections": [
- {
- "fromIndex": 0,
- "toIndex": 3,
- "type": "Success"
- },
- {
- "fromIndex": 1,
- "toIndex": 7,
- "type": "Success"
- },
- {
- "fromIndex": 2,
- "toIndex": 7,
- "type": "Success"
- },
- {
- "fromIndex": 3,
- "toIndex": 6,
- "type": "RPC Request to Device"
- },
- {
- "fromIndex": 3,
- "toIndex": 5,
- "type": "Other"
- },
- {
- "fromIndex": 3,
- "toIndex": 2,
- "type": "Post attributes"
- },
- {
- "fromIndex": 3,
- "toIndex": 1,
- "type": "Post telemetry"
- },
- {
- "fromIndex": 3,
- "toIndex": 4,
- "type": "RPC Request from Device"
- },
- {
- "fromIndex": 4,
- "toIndex": 7,
- "type": "Success"
- }
- ],
- "ruleChainConnections": null
- }
-}
\ No newline at end of file
diff --git a/application/src/main/data/json/system/widget_bundles/control_widgets.json b/application/src/main/data/json/system/widget_bundles/control_widgets.json
index 1de1bae6f1..130c0c6dd5 100644
--- a/application/src/main/data/json/system/widget_bundles/control_widgets.json
+++ b/application/src/main/data/json/system/widget_bundles/control_widgets.json
@@ -18,8 +18,8 @@
"resources": [],
"templateHtml": "
",
"templateCss": ".cmd .cursor.blink {\n -webkit-animation-name: terminal-underline;\n -moz-animation-name: terminal-underline;\n -ms-animation-name: terminal-underline;\n animation-name: terminal-underline;\n}\n.terminal .inverted, .cmd .inverted {\n border-bottom-color: #aaa;\n}\n\n",
- "controllerScript": "var requestTimeout = 500;\n\nself.onInit = function() {\n var subscription = self.ctx.defaultSubscription;\n var rpcEnabled = subscription.rpcEnabled;\n var deviceName = 'Simulated';\n var prompt;\n if (subscription.targetDeviceName && subscription.targetDeviceName.length) {\n deviceName = subscription.targetDeviceName;\n }\n if (self.ctx.settings.requestTimeout) {\n requestTimeout = self.ctx.settings.requestTimeout;\n }\n var greetings = 'Welcome to ThingsBoard RPC debug terminal.\\n\\n';\n if (!rpcEnabled) {\n greetings += 'Target device is not set!\\n\\n';\n prompt = '';\n } else {\n greetings += 'Current target device for RPC commands: [[b;#fff;]' + deviceName + ']\\n\\n';\n greetings += 'Please type [[b;#fff;]\\'help\\'] to see usage.\\n';\n prompt = '[[b;#8bc34a;]' + deviceName +']> ';\n }\n \n var terminal = $('#device-terminal', self.ctx.$container).terminal(\n function(command) {\n if (command !== '') {\n try {\n var localCommand = command.trim();\n var requestUUID = uuidv4();\n if (localCommand === 'help') {\n printUsage(this);\n } else {\n var spaceIndex = localCommand.indexOf(' ');\n if (spaceIndex === -1 && !localCommand.length) {\n this.error(\"Wrong number of arguments!\");\n this.echo(' ');\n } else {\n var params;\n if (spaceIndex === -1) {\n spaceIndex = localCommand.length;\n }\n var name = localCommand.substr(0, spaceIndex);\n var args = localCommand.substr(spaceIndex + 1);\n if (args.length) {\n try {\n params = JSON.parse(args);\n } catch (e) {\n params = args;\n }\n }\n performRpc(this, name, params, requestUUID);\n }\n }\n } catch(e) {\n this.error(new String(e));\n }\n } else {\n this.echo('');\n }\n }, {\n greetings: greetings,\n prompt: prompt,\n enabled: rpcEnabled\n });\n \n if (!rpcEnabled) {\n terminal.error('No RPC target detected!').pause();\n }\n}\n\n\nfunction printUsage(terminal) {\n var commandsListText = '\\n[[b;#fff;]Usage:]\\n';\n commandsListText += ' [params body]]\\n\\n';\n commandsListText += '[[b;#fff;]Example 1:]\\n'; \n commandsListText += ' myRemoteMethod1 myText\\n\\n'; \n commandsListText += '[[b;#fff;]Example 2:]\\n'; \n commandsListText += ' myOtherRemoteMethod \"{\\\\\"key1\\\\\": 2, \\\\\"key2\\\\\": \\\\\"myVal\\\\\"}\"\\n'; \n terminal.echo(new String(commandsListText));\n}\n\n\nfunction performRpc(terminal, method, params, requestUUID) {\n terminal.pause();\n self.ctx.controlApi.sendTwoWayCommand(method, params, requestTimeout, requestUUID).subscribe(\n function success(responseBody) {\n terminal.echo(JSON.stringify(responseBody));\n terminal.echo(' ');\n terminal.resume();\n },\n function fail() {\n var errorText = self.ctx.defaultSubscription.rpcErrorText;\n terminal.error(errorText);\n terminal.echo(' ');\n terminal.resume();\n }\n );\n}\n\n\nfunction uuidv4() {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n}\n\n \nself.onDestroy = function() {\n}",
- "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"requestTimeout\": {\n \"title\": \"RPC request timeout (ms)\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"requestTimeout\"\n ]\n}",
+ "controllerScript": "var requestTimeout = 500;\nvar requestPersistent = false;\n\nself.onInit = function() {\n var subscription = self.ctx.defaultSubscription;\n var rpcEnabled = subscription.rpcEnabled;\n var deviceName = 'Simulated';\n var prompt;\n if (subscription.targetDeviceName && subscription.targetDeviceName.length) {\n deviceName = subscription.targetDeviceName;\n }\n if (self.ctx.settings.requestTimeout) {\n requestTimeout = self.ctx.settings.requestTimeout;\n }\n if (self.ctx.settings.requestPersistent) {\n requestPersistent = self.ctx.settings.requestPersistent;\n }\n var greetings = 'Welcome to ThingsBoard RPC debug terminal.\\n\\n';\n if (!rpcEnabled) {\n greetings += 'Target device is not set!\\n\\n';\n prompt = '';\n } else {\n greetings += 'Current target device for RPC commands: [[b;#fff;]' + deviceName + ']\\n\\n';\n greetings += 'Please type [[b;#fff;]\\'help\\'] to see usage.\\n';\n prompt = '[[b;#8bc34a;]' + deviceName +']> ';\n }\n \n var terminal = $('#device-terminal', self.ctx.$container).terminal(\n function(command) {\n if (command !== '') {\n try {\n var localCommand = command.trim();\n var requestUUID = uuidv4();\n if (localCommand === 'help') {\n printUsage(this);\n } else {\n var spaceIndex = localCommand.indexOf(' ');\n if (spaceIndex === -1 && !localCommand.length) {\n this.error(\"Wrong number of arguments!\");\n this.echo(' ');\n } else {\n var params;\n if (spaceIndex === -1) {\n spaceIndex = localCommand.length;\n }\n var name = localCommand.substr(0, spaceIndex);\n var args = localCommand.substr(spaceIndex + 1);\n if (args.length) {\n try {\n params = JSON.parse(args);\n } catch (e) {\n params = args;\n }\n }\n performRpc(this, name, params, requestUUID);\n }\n }\n } catch(e) {\n this.error(new String(e));\n }\n } else {\n this.echo('');\n }\n }, {\n greetings: greetings,\n prompt: prompt,\n enabled: rpcEnabled\n });\n \n if (!rpcEnabled) {\n terminal.error('No RPC target detected!').pause();\n }\n}\n\n\nfunction printUsage(terminal) {\n var commandsListText = '\\n[[b;#fff;]Usage:]\\n';\n commandsListText += ' [params body]]\\n\\n';\n commandsListText += '[[b;#fff;]Example 1:]\\n'; \n commandsListText += ' myRemoteMethod1 myText\\n\\n'; \n commandsListText += '[[b;#fff;]Example 2:]\\n'; \n commandsListText += ' myOtherRemoteMethod \"{\\\\\"key1\\\\\": 2, \\\\\"key2\\\\\": \\\\\"myVal\\\\\"}\"\\n'; \n terminal.echo(new String(commandsListText));\n}\n\n\nfunction performRpc(terminal, method, params, requestUUID) {\n terminal.pause();\n self.ctx.controlApi.sendTwoWayCommand(method, params, requestTimeout, requestPersistent, requestUUID).subscribe(\n function success(responseBody) {\n terminal.echo(JSON.stringify(responseBody));\n terminal.echo(' ');\n terminal.resume();\n },\n function fail() {\n var errorText = self.ctx.defaultSubscription.rpcErrorText;\n terminal.error(errorText);\n terminal.echo(' ');\n terminal.resume();\n }\n );\n}\n\n\nfunction uuidv4() {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n}\n\n \nself.onDestroy = function() {\n}",
+ "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"requestTimeout\": {\n \"title\": \"RPC request timeout (ms)\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"requestPersistent\": {\n \"title\": \"RPC request persistent\",\n \"type\": \"boolean\",\n \"default\": false\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"requestTimeout\",\n \"requestPersistent\"\n ]\n}",
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":true,\"backgroundColor\":\"#010101\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"parseGpioStatusFunction\":\"return body[pin] === true;\",\"gpioStatusChangeRequest\":{\"method\":\"setGpioStatus\",\"paramsBody\":\"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"},\"requestTimeout\":500,\"switchPanelBackgroundColor\":\"#b71c1c\",\"gpioStatusRequest\":{\"method\":\"getGpioStatus\",\"paramsBody\":\"{}\"},\"gpioList\":[{\"pin\":1,\"label\":\"GPIO 1\",\"row\":0,\"col\":0,\"_uniqueKey\":0},{\"pin\":2,\"label\":\"GPIO 2\",\"row\":0,\"col\":1,\"_uniqueKey\":1},{\"pin\":3,\"label\":\"GPIO 3\",\"row\":1,\"col\":0,\"_uniqueKey\":2}]},\"title\":\"RPC debug terminal\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
}
diff --git a/application/src/main/data/json/tenant/edge_management/rule_chains/edge_root_rule_chain.json b/application/src/main/data/json/tenant/edge_management/rule_chains/edge_root_rule_chain.json
index 740d64627e..a065279cce 100644
--- a/application/src/main/data/json/tenant/edge_management/rule_chains/edge_root_rule_chain.json
+++ b/application/src/main/data/json/tenant/edge_management/rule_chains/edge_root_rule_chain.json
@@ -14,8 +14,8 @@
{
"additionalInfo": {
"description": "Process incoming messages from devices with the alarm rules defined in the device profile. Dispatch all incoming messages with \"Success\" relation type.",
- "layoutX": 203,
- "layoutY": 259
+ "layoutX": 187,
+ "layoutY": 468
},
"type": "org.thingsboard.rule.engine.profile.TbDeviceProfileNode",
"name": "Device Profile Node",
@@ -99,14 +99,14 @@
},
{
"additionalInfo": {
- "layoutX": 1134,
- "layoutY": 132
+ "layoutX": 1129,
+ "layoutY": 52
},
"type": "org.thingsboard.rule.engine.edge.TbMsgPushToCloudNode",
"name": "Push to cloud",
"debugMode": false,
"configuration": {
- "version": 0
+ "scope": "SERVER_SCOPE"
}
}
],
@@ -151,6 +151,11 @@
"toIndex": 4,
"type": "RPC Request from Device"
},
+ {
+ "fromIndex": 3,
+ "toIndex": 7,
+ "type": "Attributes Updated"
+ },
{
"fromIndex": 4,
"toIndex": 7,
diff --git a/application/src/main/data/upgrade/2.4.3/schema_update_psql_drop_partitions.sql b/application/src/main/data/upgrade/2.4.3/schema_update_psql_drop_partitions.sql
index fcc5c6f232..9d336e0330 100644
--- a/application/src/main/data/upgrade/2.4.3/schema_update_psql_drop_partitions.sql
+++ b/application/src/main/data/upgrade/2.4.3/schema_update_psql_drop_partitions.sql
@@ -18,17 +18,18 @@ CREATE OR REPLACE PROCEDURE drop_partitions_by_max_ttl(IN partition_type varchar
LANGUAGE plpgsql AS
$$
DECLARE
- max_tenant_ttl bigint;
- max_customer_ttl bigint;
- max_ttl bigint;
- date timestamp;
- partition_by_max_ttl_date varchar;
- partition_month varchar;
- partition_day varchar;
- partition_year varchar;
- partition varchar;
- partition_to_delete varchar;
-
+ max_tenant_ttl bigint;
+ max_customer_ttl bigint;
+ max_ttl bigint;
+ date timestamp;
+ partition_by_max_ttl_date varchar;
+ partition_by_max_ttl_month varchar;
+ partition_by_max_ttl_day varchar;
+ partition_by_max_ttl_year varchar;
+ partition varchar;
+ partition_year integer;
+ partition_month integer;
+ partition_day integer;
BEGIN
SELECT max(attribute_kv.long_v)
@@ -45,53 +46,138 @@ BEGIN
if max_ttl IS NOT NULL AND max_ttl > 0 THEN
date := to_timestamp(EXTRACT(EPOCH FROM current_timestamp) - max_ttl);
partition_by_max_ttl_date := get_partition_by_max_ttl_date(partition_type, date);
+ RAISE NOTICE 'Date by max ttl: %', date;
RAISE NOTICE 'Partition by max ttl: %', partition_by_max_ttl_date;
IF partition_by_max_ttl_date IS NOT NULL THEN
CASE
WHEN partition_type = 'DAYS' THEN
- partition_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3);
- partition_month := SPLIT_PART(partition_by_max_ttl_date, '_', 4);
- partition_day := SPLIT_PART(partition_by_max_ttl_date, '_', 5);
+ partition_by_max_ttl_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3);
+ partition_by_max_ttl_month := SPLIT_PART(partition_by_max_ttl_date, '_', 4);
+ partition_by_max_ttl_day := SPLIT_PART(partition_by_max_ttl_date, '_', 5);
WHEN partition_type = 'MONTHS' THEN
- partition_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3);
- partition_month := SPLIT_PART(partition_by_max_ttl_date, '_', 4);
+ partition_by_max_ttl_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3);
+ partition_by_max_ttl_month := SPLIT_PART(partition_by_max_ttl_date, '_', 4);
ELSE
- partition_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3);
+ partition_by_max_ttl_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3);
END CASE;
- FOR partition IN SELECT tablename
- FROM pg_tables
- WHERE schemaname = 'public'
- AND tablename like 'ts_kv_' || '%'
- AND tablename != 'ts_kv_latest'
- AND tablename != 'ts_kv_dictionary'
- AND tablename != 'ts_kv_indefinite'
- LOOP
- IF partition != partition_by_max_ttl_date THEN
- IF partition_year IS NOT NULL THEN
- IF SPLIT_PART(partition, '_', 3)::integer < partition_year::integer THEN
- partition_to_delete := partition;
- ELSE
- IF partition_month IS NOT NULL THEN
- IF SPLIT_PART(partition, '_', 4)::integer < partition_month::integer THEN
- partition_to_delete := partition;
+ IF partition_by_max_ttl_year IS NULL THEN
+ RAISE NOTICE 'Failed to remove partitions by max ttl date due to partition_by_max_ttl_year is null!';
+ ELSE
+ IF partition_type = 'YEARS' THEN
+ FOR partition IN SELECT tablename
+ FROM pg_tables
+ WHERE schemaname = 'public'
+ AND tablename like 'ts_kv_' || '%'
+ AND tablename != 'ts_kv_latest'
+ AND tablename != 'ts_kv_dictionary'
+ AND tablename != 'ts_kv_indefinite'
+ AND tablename != partition_by_max_ttl_date
+ LOOP
+ partition_year := SPLIT_PART(partition, '_', 3)::integer;
+ IF partition_year < partition_by_max_ttl_year::integer THEN
+ RAISE NOTICE 'Partition to delete by max ttl: %', partition;
+ EXECUTE format('DROP TABLE IF EXISTS %I', partition);
+ deleted := deleted + 1;
+ END IF;
+ END LOOP;
+ ELSE
+ IF partition_type = 'MONTHS' THEN
+ IF partition_by_max_ttl_month IS NULL THEN
+ RAISE NOTICE 'Failed to remove months partitions by max ttl date due to partition_by_max_ttl_month is null!';
+ ELSE
+ FOR partition IN SELECT tablename
+ FROM pg_tables
+ WHERE schemaname = 'public'
+ AND tablename like 'ts_kv_' || '%'
+ AND tablename != 'ts_kv_latest'
+ AND tablename != 'ts_kv_dictionary'
+ AND tablename != 'ts_kv_indefinite'
+ AND tablename != partition_by_max_ttl_date
+ LOOP
+ partition_year := SPLIT_PART(partition, '_', 3)::integer;
+ IF partition_year > partition_by_max_ttl_year::integer THEN
+ RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition;
+ CONTINUE;
ELSE
- IF partition_day IS NOT NULL THEN
- IF SPLIT_PART(partition, '_', 5)::integer < partition_day::integer THEN
- partition_to_delete := partition;
+ IF partition_year < partition_by_max_ttl_year::integer THEN
+ RAISE NOTICE 'Partition to delete by max ttl: %', partition;
+ EXECUTE format('DROP TABLE IF EXISTS %I', partition);
+ deleted := deleted + 1;
+ ELSE
+ partition_month := SPLIT_PART(partition, '_', 4)::integer;
+ IF partition_year = partition_by_max_ttl_year::integer THEN
+ IF partition_month >= partition_by_max_ttl_month::integer THEN
+ RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition;
+ CONTINUE;
+ ELSE
+ RAISE NOTICE 'Partition to delete by max ttl: %', partition;
+ EXECUTE format('DROP TABLE IF EXISTS %I', partition);
+ deleted := deleted + 1;
+ END IF;
END IF;
END IF;
END IF;
+ END LOOP;
+ END IF;
+ ELSE
+ IF partition_type = 'DAYS' THEN
+ IF partition_by_max_ttl_month IS NULL THEN
+ RAISE NOTICE 'Failed to remove days partitions by max ttl date due to partition_by_max_ttl_month is null!';
+ ELSE
+ IF partition_by_max_ttl_day IS NULL THEN
+ RAISE NOTICE 'Failed to remove days partitions by max ttl date due to partition_by_max_ttl_day is null!';
+ ELSE
+ FOR partition IN SELECT tablename
+ FROM pg_tables
+ WHERE schemaname = 'public'
+ AND tablename like 'ts_kv_' || '%'
+ AND tablename != 'ts_kv_latest'
+ AND tablename != 'ts_kv_dictionary'
+ AND tablename != 'ts_kv_indefinite'
+ AND tablename != partition_by_max_ttl_date
+ LOOP
+ partition_year := SPLIT_PART(partition, '_', 3)::integer;
+ IF partition_year > partition_by_max_ttl_year::integer THEN
+ RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition;
+ CONTINUE;
+ ELSE
+ IF partition_year < partition_by_max_ttl_year::integer THEN
+ RAISE NOTICE 'Partition to delete by max ttl: %', partition;
+ EXECUTE format('DROP TABLE IF EXISTS %I', partition);
+ deleted := deleted + 1;
+ ELSE
+ partition_month := SPLIT_PART(partition, '_', 4)::integer;
+ IF partition_month > partition_by_max_ttl_month::integer THEN
+ RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition;
+ CONTINUE;
+ ELSE
+ IF partition_month < partition_by_max_ttl_month::integer THEN
+ RAISE NOTICE 'Partition to delete by max ttl: %', partition;
+ EXECUTE format('DROP TABLE IF EXISTS %I', partition);
+ deleted := deleted + 1;
+ ELSE
+ partition_day := SPLIT_PART(partition, '_', 5)::integer;
+ IF partition_day >= partition_by_max_ttl_day::integer THEN
+ RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition;
+ CONTINUE;
+ ELSE
+ IF partition_day < partition_by_max_ttl_day::integer THEN
+ RAISE NOTICE 'Partition to delete by max ttl: %', partition;
+ EXECUTE format('DROP TABLE IF EXISTS %I', partition);
+ deleted := deleted + 1;
+ END IF;
+ END IF;
+ END IF;
+ END IF;
+ END IF;
+ END IF;
+ END LOOP;
END IF;
END IF;
END IF;
- IF partition_to_delete IS NOT NULL THEN
- RAISE NOTICE 'Partition to delete by max ttl: %', partition_to_delete;
- EXECUTE format('DROP TABLE IF EXISTS %I', partition_to_delete);
- partition_to_delete := NULL;
- deleted := deleted + 1;
- END IF;
END IF;
- END LOOP;
+ END IF;
+ END IF;
END IF;
END IF;
END
@@ -107,8 +193,6 @@ BEGIN
partition := 'ts_kv_' || to_char(date, 'yyyy') || '_' || to_char(date, 'MM');
WHEN partition_type = 'YEARS' THEN
partition := 'ts_kv_' || to_char(date, 'yyyy');
- WHEN partition_type = 'INDEFINITE' THEN
- partition := NULL;
ELSE
partition := NULL;
END CASE;
diff --git a/application/src/main/data/upgrade/3.2.2/schema_update.sql b/application/src/main/data/upgrade/3.2.2/schema_update.sql
index 8020046a7b..bda87bbb6b 100644
--- a/application/src/main/data/upgrade/3.2.2/schema_update.sql
+++ b/application/src/main/data/upgrade/3.2.2/schema_update.sql
@@ -197,3 +197,17 @@ $$;
ALTER TABLE api_usage_state
ADD COLUMN IF NOT EXISTS alarm_exec VARCHAR(32);
UPDATE api_usage_state SET alarm_exec = 'ENABLED' WHERE alarm_exec IS NULL;
+
+CREATE TABLE IF NOT EXISTS rpc (
+ id uuid NOT NULL CONSTRAINT rpc_pkey PRIMARY KEY,
+ created_time bigint NOT NULL,
+ tenant_id uuid NOT NULL,
+ device_id uuid NOT NULL,
+ expiration_time bigint NOT NULL,
+ request varchar(10000000) NOT NULL,
+ response varchar(10000000),
+ status varchar(255) NOT NULL
+);
+
+CREATE INDEX IF NOT EXISTS idx_rpc_tenant_id_device_id ON rpc(tenant_id, device_id);
+
diff --git a/application/src/main/data/upgrade/3.2.2/schema_update_event.sql b/application/src/main/data/upgrade/3.2.2/schema_update_event.sql
new file mode 100644
index 0000000000..9b673bc7b6
--- /dev/null
+++ b/application/src/main/data/upgrade/3.2.2/schema_update_event.sql
@@ -0,0 +1,90 @@
+--
+-- Copyright © 2016-2021 The Thingsboard Authors
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+
+-- PROCEDURE: public.cleanup_events_by_ttl(bigint, bigint, bigint)
+
+DROP PROCEDURE IF EXISTS public.cleanup_events_by_ttl(bigint, bigint, bigint);
+
+CREATE OR REPLACE PROCEDURE public.cleanup_events_by_ttl(
+ ttl bigint,
+ debug_ttl bigint,
+ INOUT deleted bigint)
+LANGUAGE 'plpgsql'
+AS $BODY$
+DECLARE
+ ttl_ts bigint;
+ debug_ttl_ts bigint;
+ ttl_deleted_count bigint DEFAULT 0;
+ debug_ttl_deleted_count bigint DEFAULT 0;
+BEGIN
+ IF ttl > 0 THEN
+ ttl_ts := (EXTRACT(EPOCH FROM current_timestamp) * 1000 - ttl::bigint * 1000)::bigint;
+
+ DELETE FROM event
+ WHERE ts < ttl_ts
+ AND NOT event_type IN ('DEBUG_RULE_NODE', 'DEBUG_RULE_CHAIN', 'DEBUG_CONVERTER', 'DEBUG_INTEGRATION');
+
+ GET DIAGNOSTICS ttl_deleted_count = ROW_COUNT;
+ END IF;
+
+ IF debug_ttl > 0 THEN
+ debug_ttl_ts := (EXTRACT(EPOCH FROM current_timestamp) * 1000 - debug_ttl::bigint * 1000)::bigint;
+
+ DELETE FROM event
+ WHERE ts < debug_ttl_ts
+ AND event_type IN ('DEBUG_RULE_NODE', 'DEBUG_RULE_CHAIN', 'DEBUG_CONVERTER', 'DEBUG_INTEGRATION');
+
+ GET DIAGNOSTICS debug_ttl_deleted_count = ROW_COUNT;
+ END IF;
+
+ RAISE NOTICE 'Events removed by ttl: %', ttl_deleted_count;
+ RAISE NOTICE 'Debug Events removed by ttl: %', debug_ttl_deleted_count;
+ deleted := ttl_deleted_count + debug_ttl_deleted_count;
+END
+$BODY$;
+
+
+-- Index: idx_event_ts
+
+DROP INDEX IF EXISTS public.idx_event_ts;
+
+-- Hint: add CONCURRENTLY to CREATE INDEX query in case of more then 1 million records or during live update
+-- CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_event_ts
+CREATE INDEX IF NOT EXISTS idx_event_ts
+ ON public.event
+ (ts DESC NULLS LAST)
+ WITH (FILLFACTOR=95);
+
+COMMENT ON INDEX public.idx_event_ts
+ IS 'This index helps to delete events by TTL using timestamp';
+
+
+-- Index: idx_event_tenant_entity_type_entity_event_type_created_time_des
+
+DROP INDEX IF EXISTS public.idx_event_tenant_entity_type_entity_event_type_created_time_des;
+
+-- CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_event_tenant_entity_type_entity_event_type_created_time_des
+CREATE INDEX IF NOT EXISTS idx_event_tenant_entity_type_entity_event_type_created_time_des
+ ON public.event
+ (tenant_id ASC, entity_type ASC, entity_id ASC, event_type ASC, created_time DESC NULLS LAST)
+ WITH (FILLFACTOR=95);
+
+COMMENT ON INDEX public.idx_event_tenant_entity_type_entity_event_type_created_time_des
+ IS 'This index helps to open latest events on UI fast';
+
+-- Index: idx_event_type_entity_id
+-- Description: replaced with more suitable idx_event_tenant_entity_type_entity_event_type_created_time_des
+DROP INDEX IF EXISTS public.idx_event_type_entity_id;
\ No newline at end of file
diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
index b52f85af92..5f97f3151f 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
+++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
@@ -65,6 +65,7 @@ import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.rule.RuleNodeStateService;
+import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantProfileService;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
@@ -80,9 +81,9 @@ import org.thingsboard.server.service.executors.ExternalCallExecutorService;
import org.thingsboard.server.service.executors.SharedEventLoopGroupService;
import org.thingsboard.server.service.mail.MailExecutorService;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
-import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.service.queue.TbClusterService;
import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService;
+import org.thingsboard.server.service.rpc.TbRpcService;
import org.thingsboard.server.service.rpc.TbRuleEngineDeviceRpcService;
import org.thingsboard.server.service.script.JsExecutorService;
import org.thingsboard.server.service.script.JsInvokeService;
@@ -303,23 +304,33 @@ public class ActorSystemContext {
@Lazy
@Autowired(required = false)
- @Getter private EdgeService edgeService;
+ @Getter
+ private EdgeService edgeService;
@Lazy
@Autowired(required = false)
- @Getter private EdgeEventService edgeEventService;
+ @Getter
+ private EdgeEventService edgeEventService;
@Lazy
@Autowired(required = false)
- @Getter private EdgeRpcService edgeRpcService;
+ @Getter
+ private EdgeRpcService edgeRpcService;
@Lazy
@Autowired(required = false)
- @Getter private ResourceService resourceService;
+ @Getter
+ private ResourceService resourceService;
@Lazy
@Autowired(required = false)
- @Getter private OtaPackageService otaPackageService;
+ @Getter
+ private OtaPackageService otaPackageService;
+
+ @Lazy
+ @Autowired(required = false)
+ @Getter
+ private TbRpcService tbRpcService;
@Value("${actors.session.max_concurrent_sessions_per_device:1}")
@Getter
diff --git a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java
index 6e2761c9c5..3e06b8387c 100644
--- a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java
@@ -46,7 +46,7 @@ public class DeviceActor extends ContextAwareActor {
super.init(ctx);
log.debug("[{}][{}] Starting device actor.", processor.tenantId, processor.deviceId);
try {
- processor.initSessionTimeout(ctx);
+ processor.init(ctx);
log.debug("[{}][{}] Device actor started.", processor.tenantId, processor.deviceId);
} catch (Exception e) {
log.warn("[{}][{}] Unknown failure", processor.tenantId, processor.deviceId, e);
diff --git a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
index 747a16a5e6..30eb24a4d2 100644
--- a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
@@ -23,6 +23,8 @@ import com.google.common.util.concurrent.MoreExecutors;
import com.google.protobuf.InvalidProtocolBufferException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
+import org.thingsboard.common.util.JacksonUtil;
+import org.thingsboard.common.util.LinkedHashMapRemoveEldest;
import org.thingsboard.rule.engine.api.RpcError;
import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg;
import org.thingsboard.rule.engine.api.msg.DeviceCredentialsUpdateNotificationMsg;
@@ -38,12 +40,17 @@ import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EdgeId;
+import org.thingsboard.server.common.data.id.RpcId;
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.page.PageData;
+import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
+import org.thingsboard.server.common.data.rpc.Rpc;
+import org.thingsboard.server.common.data.rpc.RpcStatus;
import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType;
@@ -52,8 +59,8 @@ import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
import org.thingsboard.server.common.msg.timeout.DeviceActorServerSideRpcTimeoutMsg;
-import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceSessionsCacheEntry;
import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg;
@@ -68,10 +75,12 @@ import org.thingsboard.server.gen.transport.TransportProtos.SessionType;
import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToAttributeUpdatesMsg;
import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToRPCMsg;
import org.thingsboard.server.gen.transport.TransportProtos.SubscriptionInfoProto;
+import org.thingsboard.server.gen.transport.TransportProtos.ToDevicePersistedRpcResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.ToTransportUpdateCredentialsProto;
import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg;
import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto;
import org.thingsboard.server.service.rpc.FromDeviceRpcResponse;
@@ -88,6 +97,8 @@ import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.function.Consumer;
@@ -102,7 +113,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
final TenantId tenantId;
final DeviceId deviceId;
- private final Map sessions;
+ final LinkedHashMapRemoveEldest sessions;
private final Map attributeSubscriptions;
private final Map rpcSubscriptions;
private final Map toDeviceRpcPendingMap;
@@ -117,16 +128,16 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
super(systemContext);
this.tenantId = tenantId;
this.deviceId = deviceId;
- this.sessions = new LinkedHashMap<>();
this.attributeSubscriptions = new HashMap<>();
this.rpcSubscriptions = new HashMap<>();
this.toDeviceRpcPendingMap = new HashMap<>();
+ this.sessions = new LinkedHashMapRemoveEldest<>(systemContext.getMaxConcurrentSessionsPerDevice(), this::notifyTransportAboutClosedSessionMaxSessionsLimit);
if (initAttributes()) {
restoreSessions();
}
}
- private boolean initAttributes() {
+ boolean initAttributes() {
Device device = systemContext.getDeviceService().findDeviceById(tenantId, deviceId);
if (device != null) {
this.deviceName = device.getName();
@@ -162,20 +173,19 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
void processRpcRequest(TbActorCtx context, ToDeviceRpcRequestActorMsg msg) {
ToDeviceRpcRequest request = msg.getMsg();
- ToDeviceRpcRequestBody body = request.getBody();
- ToDeviceRpcRequestMsg rpcRequest = ToDeviceRpcRequestMsg.newBuilder()
- .setRequestId(rpcSeq++)
- .setMethodName(body.getMethod())
- .setParams(body.getParams())
- .setExpirationTime(request.getExpirationTime())
- .setRequestIdMSB(request.getId().getMostSignificantBits())
- .setRequestIdLSB(request.getId().getLeastSignificantBits())
- .build();
+ ToDeviceRpcRequestMsg rpcRequest = creteToDeviceRpcRequestMsg(request);
long timeout = request.getExpirationTime() - System.currentTimeMillis();
+ boolean persisted = request.isPersisted();
+
if (timeout <= 0) {
log.debug("[{}][{}] Ignoring message due to exp time reached, {}", deviceId, request.getId(), request.getExpirationTime());
+ if (persisted) {
+ createRpc(request, RpcStatus.TIMEOUT);
+ }
return;
+ } else if (persisted) {
+ createRpc(request, RpcStatus.QUEUED);
}
boolean sent;
@@ -192,11 +202,17 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
syncSessionSet.add(key);
}
});
- log.trace("46) Rpc syncSessionSet [{}] subscription after sent [{}]",syncSessionSet, rpcSubscriptions);
+ log.trace("46) Rpc syncSessionSet [{}] subscription after sent [{}]", syncSessionSet, rpcSubscriptions);
syncSessionSet.forEach(rpcSubscriptions::remove);
}
- if (request.isOneway() && sent) {
+ if (persisted) {
+ ObjectNode response = JacksonUtil.newObjectNode();
+ response.put("rpcId", request.getId().toString());
+ systemContext.getTbCoreDeviceRpcService().processRpcResponseFromDeviceActor(new FromDeviceRpcResponse(msg.getMsg().getId(), JacksonUtil.toString(response), null));
+ }
+
+ if (!persisted && request.isOneway() && sent) {
log.debug("[{}] Rpc command response sent [{}]!", deviceId, request.getId());
systemContext.getTbCoreDeviceRpcService().processRpcResponseFromDeviceActor(new FromDeviceRpcResponse(msg.getMsg().getId(), null, null));
} else {
@@ -209,6 +225,31 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
}
}
+ private Rpc createRpc(ToDeviceRpcRequest request, RpcStatus status) {
+ Rpc rpc = new Rpc(new RpcId(request.getId()));
+ rpc.setCreatedTime(System.currentTimeMillis());
+ rpc.setTenantId(tenantId);
+ rpc.setDeviceId(deviceId);
+ rpc.setExpirationTime(request.getExpirationTime());
+ rpc.setRequest(JacksonUtil.valueToTree(request));
+ rpc.setStatus(status);
+ return systemContext.getTbRpcService().save(tenantId, rpc);
+ }
+
+ private ToDeviceRpcRequestMsg creteToDeviceRpcRequestMsg(ToDeviceRpcRequest request) {
+ ToDeviceRpcRequestBody body = request.getBody();
+ return ToDeviceRpcRequestMsg.newBuilder()
+ .setRequestId(rpcSeq++)
+ .setMethodName(body.getMethod())
+ .setParams(body.getParams())
+ .setExpirationTime(request.getExpirationTime())
+ .setRequestIdMSB(request.getId().getMostSignificantBits())
+ .setRequestIdLSB(request.getId().getLeastSignificantBits())
+ .setOneway(request.isOneway())
+ .setPersisted(request.isPersisted())
+ .build();
+ }
+
void processRpcResponsesFromEdge(TbActorCtx context, FromDeviceRpcResponseActorMsg responseMsg) {
log.debug("[{}] Processing rpc command response from edge session", deviceId);
ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(responseMsg.getRequestId());
@@ -230,6 +271,9 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(msg.getId());
if (requestMd != null) {
log.debug("[{}] RPC request [{}] timeout detected!", deviceId, msg.getId());
+ if (requestMd.getMsg().getMsg().isPersisted()) {
+ systemContext.getTbRpcService().save(tenantId, new RpcId(requestMd.getMsg().getMsg().getId()), RpcStatus.TIMEOUT, null);
+ }
systemContext.getTbCoreDeviceRpcService().processRpcResponseFromDeviceActor(new FromDeviceRpcResponse(requestMd.getMsg().getMsg().getId(),
null, requestMd.isSent() ? RpcError.TIMEOUT : RpcError.NO_ACTIVE_CONNECTION));
}
@@ -253,7 +297,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
toDeviceRpcPendingMap.entrySet().stream().findFirst().ifPresent(processPendingRpc(context, sessionId, sessionInfo.getNodeId(), sentOneWayIds));
}
- sentOneWayIds.forEach(toDeviceRpcPendingMap::remove);
+ sentOneWayIds.stream().filter(id -> !toDeviceRpcPendingMap.get(id).getMsg().getMsg().isPersisted()).forEach(toDeviceRpcPendingMap::remove);
}
private Consumer> processPendingRpc(TbActorCtx context, UUID sessionId, String nodeId, Set sentOneWayIds) {
@@ -271,7 +315,10 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
.setExpirationTime(request.getExpirationTime())
.setRequestIdMSB(request.getId().getMostSignificantBits())
.setRequestIdLSB(request.getId().getLeastSignificantBits())
+ .setOneway(request.isOneway())
+ .setPersisted(request.isPersisted())
.build();
+
sendToTransport(rpcRequest, sessionId, nodeId);
};
}
@@ -279,31 +326,39 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
void process(TbActorCtx context, TransportToDeviceActorMsgWrapper wrapper) {
TransportToDeviceActorMsg msg = wrapper.getMsg();
TbCallback callback = wrapper.getCallback();
+ var sessionInfo = msg.getSessionInfo();
+
if (msg.hasSessionEvent()) {
- processSessionStateMsgs(msg.getSessionInfo(), msg.getSessionEvent());
+ processSessionStateMsgs(sessionInfo, msg.getSessionEvent());
}
if (msg.hasSubscribeToAttributes()) {
- processSubscriptionCommands(context, msg.getSessionInfo(), msg.getSubscribeToAttributes());
+ processSubscriptionCommands(context, sessionInfo, msg.getSubscribeToAttributes());
}
if (msg.hasSubscribeToRPC()) {
- processSubscriptionCommands(context, msg.getSessionInfo(), msg.getSubscribeToRPC());
+ processSubscriptionCommands(context, sessionInfo, msg.getSubscribeToRPC());
+ }
+ if (msg.hasSendPendingRPC()) {
+ sendPendingRequests(context, getSessionId(sessionInfo), sessionInfo);
}
if (msg.hasGetAttributes()) {
- handleGetAttributesRequest(context, msg.getSessionInfo(), msg.getGetAttributes());
+ handleGetAttributesRequest(context, sessionInfo, msg.getGetAttributes());
}
if (msg.hasToDeviceRPCCallResponse()) {
- processRpcResponses(context, msg.getSessionInfo(), msg.getToDeviceRPCCallResponse());
+ processRpcResponses(context, sessionInfo, msg.getToDeviceRPCCallResponse());
}
if (msg.hasSubscriptionInfo()) {
- handleSessionActivity(context, msg.getSessionInfo(), msg.getSubscriptionInfo());
+ handleSessionActivity(context, sessionInfo, msg.getSubscriptionInfo());
}
if (msg.hasClaimDevice()) {
- handleClaimDeviceMsg(context, msg.getSessionInfo(), msg.getClaimDevice());
+ handleClaimDeviceMsg(context, sessionInfo, msg.getClaimDevice());
+ }
+ if (msg.hasPersistedRpcResponseMsg()) {
+ processPersistedRpcResponses(context, sessionInfo, msg.getPersistedRpcResponseMsg());
}
callback.onSuccess();
}
- private void handleClaimDeviceMsg(TbActorCtx context, SessionInfoProto sessionInfo, TransportProtos.ClaimDeviceMsg msg) {
+ private void handleClaimDeviceMsg(TbActorCtx context, SessionInfoProto sessionInfo, ClaimDeviceMsg msg) {
DeviceId deviceId = new DeviceId(new UUID(msg.getDeviceIdMSB(), msg.getDeviceIdLSB()));
systemContext.getClaimDevicesService().registerClaimingInfo(tenantId, deviceId, msg.getSecretKey(), msg.getDurationMs());
}
@@ -442,11 +497,19 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
if (success) {
systemContext.getTbCoreDeviceRpcService().processRpcResponseFromDeviceActor(new FromDeviceRpcResponse(requestMd.getMsg().getMsg().getId(),
responseMsg.getPayload(), null));
+ if (requestMd.getMsg().getMsg().isPersisted()) {
+ systemContext.getTbRpcService().save(tenantId, new RpcId(requestMd.getMsg().getMsg().getId()), RpcStatus.SUCCESSFUL, JacksonUtil.toJsonNode(responseMsg.getPayload()));
+ }
} else {
log.debug("[{}] Rpc command response [{}] is stale!", deviceId, responseMsg.getRequestId());
}
}
+ private void processPersistedRpcResponses(TbActorCtx context, SessionInfoProto sessionInfo, ToDevicePersistedRpcResponseMsg responseMsg) {
+ UUID rpcId = new UUID(responseMsg.getRequestIdMSB(), responseMsg.getRequestIdLSB());
+ systemContext.getTbRpcService().save(tenantId, new RpcId(rpcId), RpcStatus.valueOf(responseMsg.getStatus()), null);
+ }
+
private void processSubscriptionCommands(TbActorCtx context, SessionInfoProto sessionInfo, SubscribeToAttributeUpdatesMsg subscribeCmd) {
UUID sessionId = getSessionId(sessionInfo);
if (subscribeCmd.getUnsubscribe()) {
@@ -488,18 +551,14 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
private void processSessionStateMsgs(SessionInfoProto sessionInfo, SessionEventMsg msg) {
UUID sessionId = getSessionId(sessionInfo);
+ Objects.requireNonNull(sessionId);
if (msg.getEvent() == SessionEvent.OPEN) {
if (sessions.containsKey(sessionId)) {
log.debug("[{}] Received duplicate session open event [{}]", deviceId, sessionId);
return;
}
- log.debug("[{}] Processing new session [{}]", deviceId, sessionId);
- if (sessions.size() >= systemContext.getMaxConcurrentSessionsPerDevice()) {
- UUID sessionIdToRemove = sessions.keySet().stream().findFirst().orElse(null);
- if (sessionIdToRemove != null) {
- notifyTransportAboutClosedSession(sessionIdToRemove, sessions.remove(sessionIdToRemove), "max concurrent sessions limit reached per device!");
- }
- }
+ log.info("[{}] Processing new session [{}]. Current sessions size {}", deviceId, sessionId, sessions.size());
+
sessions.put(sessionId, new SessionInfoMetaData(new SessionInfo(SessionType.ASYNC, sessionInfo.getNodeId())));
if (sessions.size() == 1) {
reportSessionOpen();
@@ -520,8 +579,10 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
private void handleSessionActivity(TbActorCtx context, SessionInfoProto sessionInfoProto, SubscriptionInfoProto subscriptionInfo) {
UUID sessionId = getSessionId(sessionInfoProto);
+ Objects.requireNonNull(sessionId);
+
SessionInfoMetaData sessionMD = sessions.computeIfAbsent(sessionId,
- id -> new SessionInfoMetaData(new SessionInfo(SessionType.ASYNC, sessionInfoProto.getNodeId()), 0L));
+ id -> new SessionInfoMetaData(new SessionInfo(SessionType.ASYNC, sessionInfoProto.getNodeId()), subscriptionInfo.getLastActivityTime()));
sessionMD.setLastActivityTime(subscriptionInfo.getLastActivityTime());
sessionMD.setSubscribedToAttributes(subscriptionInfo.getAttributeSubscription());
@@ -551,6 +612,11 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
}
}
+ private void notifyTransportAboutClosedSessionMaxSessionsLimit(UUID sessionId, SessionInfoMetaData sessionMd) {
+ notifyTransportAboutClosedSession(sessionId, sessionMd, "max concurrent sessions limit reached per device!");
+ }
+
+
private void notifyTransportAboutClosedSession(UUID sessionId, SessionInfoMetaData sessionMd, String message) {
SessionCloseNotificationProto sessionCloseNotificationProto = SessionCloseNotificationProto
.newBuilder()
@@ -565,7 +631,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
void notifyTransportAboutProfileUpdate(UUID sessionId, SessionInfoMetaData sessionMd, DeviceCredentials deviceCredentials) {
log.info("2) LwM2Mtype: ");
- TransportProtos.ToTransportUpdateCredentialsProto.Builder notification = TransportProtos.ToTransportUpdateCredentialsProto.newBuilder();
+ ToTransportUpdateCredentialsProto.Builder notification = ToTransportUpdateCredentialsProto.newBuilder();
notification.addCredentialsId(deviceCredentials.getCredentialsId());
notification.addCredentialsValue(deviceCredentials.getCredentialsValue());
ToTransportMsg msg = ToTransportMsg.newBuilder()
@@ -640,7 +706,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
ListenableFuture future = systemContext.getEdgeEventService().saveAsync(edgeEvent);
Futures.addCallback(future, new FutureCallback() {
@Override
- public void onSuccess( EdgeEvent result) {
+ public void onSuccess(EdgeEvent result) {
systemContext.getClusterService().onEdgeEventUpdate(tenantId, edgeId);
}
@@ -697,7 +763,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
return builder.build();
}
- private void restoreSessions() {
+ void restoreSessions() {
log.debug("[{}] Restoring sessions from cache", deviceId);
DeviceSessionsCacheEntry sessionsDump = null;
try {
@@ -710,6 +776,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
log.debug("[{}] No session information found", deviceId);
return;
}
+ // TODO: Take latest max allowed sessions size from cache
for (SessionSubscriptionInfoProto sessionSubscriptionInfoProto : sessionsDump.getSessionsList()) {
SessionInfoProto sessionInfoProto = sessionSubscriptionInfoProto.getSessionInfo();
UUID sessionId = getSessionId(sessionInfoProto);
@@ -756,11 +823,30 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
.addAllSessions(sessionsList).build().toByteArray());
}
- void initSessionTimeout(TbActorCtx ctx) {
+ void init(TbActorCtx ctx) {
schedulePeriodicMsgWithDelay(ctx, SessionTimeoutCheckMsg.instance(), systemContext.getSessionReportTimeout(), systemContext.getSessionReportTimeout());
+ PageLink pageLink = new PageLink(1024);
+ PageData pageData;
+ do {
+ pageData = systemContext.getTbRpcService().findAllByDeviceIdAndStatus(tenantId, deviceId, RpcStatus.QUEUED, pageLink);
+ pageData.getData().forEach(rpc -> {
+ ToDeviceRpcRequest msg = JacksonUtil.convertValue(rpc.getRequest(), ToDeviceRpcRequest.class);
+ long timeout = rpc.getExpirationTime() - System.currentTimeMillis();
+ if (timeout <= 0) {
+ rpc.setStatus(RpcStatus.TIMEOUT);
+ systemContext.getTbRpcService().save(tenantId, rpc);
+ } else {
+ registerPendingRpcRequest(ctx, new ToDeviceRpcRequestActorMsg(systemContext.getServiceId(), msg), false, creteToDeviceRpcRequestMsg(msg), timeout);
+ }
+ });
+ if (pageData.hasNext()) {
+ pageLink = pageLink.nextPageLink();
+ }
+ } while (pageData.hasNext());
}
void checkSessionsTimeout() {
+ log.debug("[{}] checkSessionsTimeout started. Size before check {}", deviceId, sessions.size());
long expTime = System.currentTimeMillis() - systemContext.getSessionInactivityTimeout();
Map sessionsToRemove = sessions.entrySet().stream().filter(kv -> kv.getValue().getLastActivityTime() < expTime).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
sessionsToRemove.forEach((sessionId, sessionMD) -> {
@@ -772,6 +858,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
if (!sessionsToRemove.isEmpty()) {
dumpSessions();
}
+ log.debug("[{}] checkSessionsTimeout finished. Size after check {}", deviceId, sessions.size());
}
}
diff --git a/application/src/main/java/org/thingsboard/server/actors/device/SessionInfo.java b/application/src/main/java/org/thingsboard/server/actors/device/SessionInfo.java
index b42855ace5..24e050e89f 100644
--- a/application/src/main/java/org/thingsboard/server/actors/device/SessionInfo.java
+++ b/application/src/main/java/org/thingsboard/server/actors/device/SessionInfo.java
@@ -25,5 +25,4 @@ import org.thingsboard.server.gen.transport.TransportProtos.SessionType;
public class SessionInfo {
private final SessionType type;
private final String nodeId;
- private long lastActivityTime;
}
diff --git a/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java b/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
index 68afbdaf99..1a74e368f5 100644
--- a/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
@@ -245,11 +245,11 @@ public class TenantActor extends RuleChainManagerActor {
EdgeId edgeId = new EdgeId(msg.getEntityId().getId());
EdgeRpcService edgeRpcService = systemContext.getEdgeRpcService();
if (msg.getEvent() == ComponentLifecycleEvent.DELETED) {
- edgeRpcService.deleteEdge(edgeId);
+ edgeRpcService.deleteEdge(tenantId, edgeId);
} else {
Edge edge = systemContext.getEdgeService().findEdgeById(tenantId, edgeId);
if (msg.getEvent() == ComponentLifecycleEvent.UPDATED) {
- edgeRpcService.updateEdge(edge);
+ edgeRpcService.updateEdge(tenantId, edge);
}
}
} else if (isRuleEngineForCurrentTenant) {
@@ -277,7 +277,7 @@ public class TenantActor extends RuleChainManagerActor {
private void onToEdgeSessionMsg(EdgeEventUpdateMsg msg) {
log.trace("[{}] onToEdgeSessionMsg [{}]", msg.getTenantId(), msg);
- systemContext.getEdgeRpcService().onEdgeEvent(msg.getEdgeId());
+ systemContext.getEdgeRpcService().onEdgeEvent(tenantId, msg.getEdgeId());
}
public static class ActorCreator extends ContextBasedCreator {
diff --git a/application/src/main/java/org/thingsboard/server/controller/AuthController.java b/application/src/main/java/org/thingsboard/server/controller/AuthController.java
index f41e40ef89..3c4db474ef 100644
--- a/application/src/main/java/org/thingsboard/server/controller/AuthController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/AuthController.java
@@ -135,7 +135,7 @@ public class AuthController extends BaseController {
}
}
- @RequestMapping(value = "/noauth/activate", params = { "activateToken" }, method = RequestMethod.GET)
+ @RequestMapping(value = "/noauth/activate", params = {"activateToken"}, method = RequestMethod.GET)
public ResponseEntity checkActivateToken(
@RequestParam(value = "activateToken") String activateToken) {
HttpHeaders headers = new HttpHeaders();
@@ -159,7 +159,7 @@ public class AuthController extends BaseController {
@RequestMapping(value = "/noauth/resetPasswordByEmail", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK)
- public void requestResetPasswordByEmail (
+ public void requestResetPasswordByEmail(
@RequestBody JsonNode resetPasswordByEmailRequest,
HttpServletRequest request) throws ThingsboardException {
try {
@@ -170,13 +170,13 @@ public class AuthController extends BaseController {
String resetUrl = String.format("%s/api/noauth/resetPassword?resetToken=%s", baseUrl,
userCredentials.getResetToken());
- mailService.sendResetPasswordEmail(resetUrl, email);
+ mailService.sendResetPasswordEmailAsync(resetUrl, email);
} catch (Exception e) {
- throw handleException(e);
+ log.warn("Error occurred: {}", e.getMessage());
}
}
- @RequestMapping(value = "/noauth/resetPassword", params = { "resetToken" }, method = RequestMethod.GET)
+ @RequestMapping(value = "/noauth/resetPassword", params = {"resetToken"}, method = RequestMethod.GET)
public ResponseEntity checkResetToken(
@RequestParam(value = "resetToken") String resetToken) {
HttpHeaders headers = new HttpHeaders();
diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java
index 7d41deb2ab..267cfe75e3 100644
--- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java
@@ -37,12 +37,12 @@ import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.EntityViewInfo;
-import org.thingsboard.server.common.data.OtaPackage;
-import org.thingsboard.server.common.data.OtaPackageInfo;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId;
-import org.thingsboard.server.common.data.TbResourceInfo;
+import org.thingsboard.server.common.data.OtaPackage;
+import org.thingsboard.server.common.data.OtaPackageInfo;
import org.thingsboard.server.common.data.TbResource;
+import org.thingsboard.server.common.data.TbResourceInfo;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantInfo;
import org.thingsboard.server.common.data.TenantProfile;
@@ -69,20 +69,23 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.OtaPackageId;
-import org.thingsboard.server.common.data.id.TbResourceId;
+import org.thingsboard.server.common.data.id.RpcId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
+import org.thingsboard.server.common.data.id.TbResourceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.id.WidgetTypeId;
import org.thingsboard.server.common.data.id.WidgetsBundleId;
+import org.thingsboard.server.common.data.page.PageDataIterableByTenantIdEntityId;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.SortOrder;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.data.relation.EntityRelation;
+import org.thingsboard.server.common.data.rpc.Rpc;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleNode;
@@ -101,11 +104,12 @@ import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
-import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService;
import org.thingsboard.server.dao.oauth2.OAuth2Service;
+import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.relation.RelationService;
+import org.thingsboard.server.dao.rpc.RpcService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantProfileService;
@@ -120,11 +124,10 @@ import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.action.RuleEngineEntityActionService;
import org.thingsboard.server.service.component.ComponentDiscoveryService;
-import org.thingsboard.server.service.ota.OtaPackageStateService;
import org.thingsboard.server.service.edge.EdgeNotificationService;
-import org.thingsboard.server.service.edge.rpc.EdgeGrpcService;
-import org.thingsboard.server.service.edge.rpc.init.SyncEdgeService;
+import org.thingsboard.server.service.edge.rpc.EdgeRpcService;
import org.thingsboard.server.service.lwm2m.LwM2MServerSecurityInfoRepository;
+import org.thingsboard.server.service.ota.OtaPackageStateService;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.queue.TbClusterService;
import org.thingsboard.server.service.resource.TbResourceService;
@@ -138,6 +141,8 @@ import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import javax.mail.MessagingException;
import javax.servlet.http.HttpServletResponse;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
@@ -153,6 +158,8 @@ public abstract class BaseController {
protected static final String DEFAULT_DASHBOARD = "defaultDashboardId";
protected static final String HOME_DASHBOARD = "homeDashboardId";
+ private static final int DEFAULT_PAGE_SIZE = 1000;
+
private static final ObjectMapper json = new ObjectMapper();
@Autowired
@@ -245,6 +252,9 @@ public abstract class BaseController {
@Autowired
protected OtaPackageStateService otaPackageStateService;
+ @Autowired
+ protected RpcService rpcService;
+
@Autowired
protected TbQueueProducerProvider producerProvider;
@@ -264,10 +274,7 @@ public abstract class BaseController {
protected EdgeNotificationService edgeNotificationService;
@Autowired(required = false)
- protected SyncEdgeService syncEdgeService;
-
- @Autowired(required = false)
- protected EdgeGrpcService edgeGrpcService;
+ protected EdgeRpcService edgeGrpcService;
@Autowired
protected RuleEngineEntityActionService ruleEngineEntityActionService;
@@ -786,6 +793,18 @@ public abstract class BaseController {
}
}
+ Rpc checkRpcId(RpcId rpcId, Operation operation) throws ThingsboardException {
+ try {
+ validateId(rpcId, "Incorrect rpcId " + rpcId);
+ Rpc rpc = rpcService.findById(getCurrentUser().getTenantId(), rpcId);
+ checkNotNull(rpc);
+ accessControlService.checkPermission(getCurrentUser(), Resource.RPC, operation, rpcId, rpc);
+ return rpc;
+ } catch (Exception e) {
+ throw handleException(e, false);
+ }
+ }
+
@SuppressWarnings("unchecked")
protected I emptyId(EntityType entityType) {
return (I) EntityIdFactory.getByTypeAndUuid(entityType, ModelConstants.NULL_UUID);
@@ -899,11 +918,14 @@ public abstract class BaseController {
if (!edgesEnabled) {
return null;
}
- List result = null;
- try {
- result = edgeService.findRelatedEdgeIdsByEntityId(tenantId, entityId).get();
- } catch (Exception e) {
- log.error("[{}] can't find related edge ids for entity [{}]", tenantId, entityId, e);
+ if (EntityType.EDGE.equals(entityId.getEntityType())) {
+ return Collections.singletonList(new EdgeId(entityId.getId()));
+ }
+ PageDataIterableByTenantIdEntityId relatedEdgeIdsIterator =
+ new PageDataIterableByTenantIdEntityId<>(edgeService::findRelatedEdgeIdsByEntityId, tenantId, entityId, DEFAULT_PAGE_SIZE);
+ List result = new ArrayList<>();
+ for(EdgeId edgeId : relatedEdgeIdsIterator) {
+ result.add(edgeId);
}
return result;
}
diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java
index b315eb2294..c261d50f23 100644
--- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java
@@ -782,15 +782,17 @@ public class DeviceController extends BaseController {
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
- @RequestMapping(value = "/devices/count/{otaPackageType}", method = RequestMethod.GET)
+ @RequestMapping(value = "/devices/count/{otaPackageType}/{deviceProfileId}", method = RequestMethod.GET)
@ResponseBody
- public Long countDevicesByTenantIdAndDeviceProfileIdAndEmptyOtaPackage(@PathVariable("otaPackageType") String otaPackageType,
- @RequestParam String deviceProfileId) throws ThingsboardException {
+ public Long countByDeviceProfileAndEmptyOtaPackage(@PathVariable("otaPackageType") String otaPackageType,
+ @PathVariable("deviceProfileId") String deviceProfileId) throws ThingsboardException {
checkParameter("OtaPackageType", otaPackageType);
checkParameter("DeviceProfileId", deviceProfileId);
try {
return deviceService.countDevicesByTenantIdAndDeviceProfileIdAndEmptyOtaPackage(
- getCurrentUser().getTenantId(), new DeviceProfileId(UUID.fromString(deviceProfileId)), OtaPackageType.valueOf(otaPackageType));
+ getTenantId(),
+ new DeviceProfileId(UUID.fromString(deviceProfileId)),
+ OtaPackageType.valueOf(otaPackageType));
} catch (Exception e) {
throw handleException(e);
}
diff --git a/application/src/main/java/org/thingsboard/server/controller/EdgeController.java b/application/src/main/java/org/thingsboard/server/controller/EdgeController.java
index e5db18d09c..426c899588 100644
--- a/application/src/main/java/org/thingsboard/server/controller/EdgeController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/EdgeController.java
@@ -49,7 +49,6 @@ import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.queue.util.TbCoreComponent;
-import org.thingsboard.server.service.edge.rpc.EdgeGrpcSession;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
@@ -128,7 +127,7 @@ public class EdgeController extends BaseController {
accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, operation,
edge.getId(), edge);
- Edge savedEdge = checkNotNull(edgeService.saveEdge(edge));
+ Edge savedEdge = checkNotNull(edgeService.saveEdge(edge, true));
if (created) {
ruleChainService.assignRuleChainToEdge(tenantId, edgeTemplateRootRuleChain.getId(), savedEdge.getId());
@@ -536,9 +535,7 @@ public class EdgeController extends BaseController {
edgeId = checkNotNull(edgeId);
SecurityUser user = getCurrentUser();
TenantId tenantId = user.getTenantId();
- EdgeGrpcSession session = edgeGrpcService.getEdgeGrpcSessionById(tenantId, edgeId);
- Edge edge = session.getEdge();
- syncEdgeService.sync(tenantId, edge);
+ edgeGrpcService.startSyncProcess(tenantId, edgeId);
} else {
throw new ThingsboardException("Edges support disabled", ThingsboardErrorCode.GENERAL);
}
diff --git a/application/src/main/java/org/thingsboard/server/controller/OtaPackageController.java b/application/src/main/java/org/thingsboard/server/controller/OtaPackageController.java
index e28c5e37e1..153b5b5c93 100644
--- a/application/src/main/java/org/thingsboard/server/controller/OtaPackageController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/OtaPackageController.java
@@ -32,12 +32,13 @@ import org.springframework.web.multipart.MultipartFile;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.OtaPackage;
import org.thingsboard.server.common.data.OtaPackageInfo;
+import org.thingsboard.server.common.data.SaveOtaPackageInfoRequest;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
-import org.thingsboard.server.common.data.ota.ChecksumAlgorithm;
-import org.thingsboard.server.common.data.ota.OtaPackageType;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.OtaPackageId;
+import org.thingsboard.server.common.data.ota.ChecksumAlgorithm;
+import org.thingsboard.server.common.data.ota.OtaPackageType;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.queue.util.TbCoreComponent;
@@ -109,12 +110,12 @@ public class OtaPackageController extends BaseController {
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/otaPackage", method = RequestMethod.POST)
@ResponseBody
- public OtaPackageInfo saveOtaPackageInfo(@RequestBody OtaPackageInfo otaPackageInfo) throws ThingsboardException {
+ public OtaPackageInfo saveOtaPackageInfo(@RequestBody SaveOtaPackageInfoRequest otaPackageInfo) throws ThingsboardException {
boolean created = otaPackageInfo.getId() == null;
try {
otaPackageInfo.setTenantId(getTenantId());
checkEntity(otaPackageInfo.getId(), otaPackageInfo, Resource.OTA_PACKAGE);
- OtaPackageInfo savedOtaPackageInfo = otaPackageService.saveOtaPackageInfo(otaPackageInfo);
+ OtaPackageInfo savedOtaPackageInfo = otaPackageService.saveOtaPackageInfo(new OtaPackageInfo(otaPackageInfo), otaPackageInfo.isUsesUrl());
logEntityAction(savedOtaPackageInfo.getId(), savedOtaPackageInfo,
null, created ? ActionType.ADDED : ActionType.UPDATED, null);
return savedOtaPackageInfo;
diff --git a/application/src/main/java/org/thingsboard/server/controller/RpcController.java b/application/src/main/java/org/thingsboard/server/controller/RpcController.java
index e71ffbc6d2..50ab5c7863 100644
--- a/application/src/main/java/org/thingsboard/server/controller/RpcController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/RpcController.java
@@ -29,17 +29,24 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.rule.engine.api.RpcError;
+import org.thingsboard.server.common.data.DataConstants;
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.RpcId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UUIDBased;
+import org.thingsboard.server.common.data.page.PageData;
+import org.thingsboard.server.common.data.page.PageLink;
+import org.thingsboard.server.common.data.rpc.Rpc;
+import org.thingsboard.server.common.data.rpc.RpcStatus;
import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
import org.thingsboard.server.queue.util.TbCoreComponent;
@@ -93,6 +100,52 @@ public class RpcController extends BaseController {
return handleDeviceRPCRequest(false, new DeviceId(UUID.fromString(deviceIdStr)), requestBody);
}
+ @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
+ @RequestMapping(value = "/persistent/{rpcId}", method = RequestMethod.GET)
+ @ResponseBody
+ public Rpc getPersistedRpc(@PathVariable("rpcId") String strRpc) throws ThingsboardException {
+ checkParameter("RpcId", strRpc);
+ try {
+ RpcId rpcId = new RpcId(UUID.fromString(strRpc));
+ return checkRpcId(rpcId, Operation.READ);
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+
+ @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
+ @RequestMapping(value = "/persistent/device/{deviceId}", method = RequestMethod.GET)
+ @ResponseBody
+ public PageData getPersistedRpcByDevice(@PathVariable("deviceId") String strDeviceId,
+ @RequestParam int pageSize,
+ @RequestParam int page,
+ @RequestParam RpcStatus rpcStatus,
+ @RequestParam(required = false) String textSearch,
+ @RequestParam(required = false) String sortProperty,
+ @RequestParam(required = false) String sortOrder) throws ThingsboardException {
+ checkParameter("DeviceId", strDeviceId);
+ try {
+ TenantId tenantId = getCurrentUser().getTenantId();
+ PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
+ DeviceId deviceId = new DeviceId(UUID.fromString(strDeviceId));
+ return checkNotNull(rpcService.findAllByDeviceIdAndStatus(tenantId, deviceId, rpcStatus, pageLink));
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+
+ @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
+ @RequestMapping(value = "/persistent/{rpcId}", method = RequestMethod.DELETE)
+ @ResponseBody
+ public void deleteResource(@PathVariable("rpcId") String strRpc) throws ThingsboardException {
+ checkParameter("RpcId", strRpc);
+ try {
+ rpcService.deleteRpc(getTenantId(), new RpcId(UUID.fromString(strRpc)));
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+
private DeferredResult handleDeviceRPCRequest(boolean oneWay, DeviceId deviceId, String requestBody) throws ThingsboardException {
try {
JsonNode rpcRequestBody = jsonMapper.readTree(requestBody);
@@ -103,6 +156,7 @@ public class RpcController extends BaseController {
long timeout = rpcRequestBody.has("timeout") ? rpcRequestBody.get("timeout").asLong() : defaultTimeout;
long expTime = System.currentTimeMillis() + Math.max(minTimeout, timeout);
UUID rpcRequestUUID = rpcRequestBody.has("requestUUID") ? UUID.fromString(rpcRequestBody.get("requestUUID").asText()) : UUID.randomUUID();
+ boolean persisted = rpcRequestBody.has(DataConstants.PERSISTENT) && rpcRequestBody.get(DataConstants.PERSISTENT).asBoolean();
accessValidator.validate(currentUser, Operation.RPC_CALL, deviceId, new HttpValidationCallback(response, new FutureCallback>() {
@Override
public void onSuccess(@Nullable DeferredResult result) {
@@ -111,7 +165,8 @@ public class RpcController extends BaseController {
deviceId,
oneWay,
expTime,
- body
+ body,
+ persisted
);
deviceRpcService.processRestApiRpcRequest(rpcRequest, fromDeviceRpcResponse -> reply(new LocalRequestMetaData(rpcRequest, currentUser, result), fromDeviceRpcResponse), currentUser);
}
diff --git a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
index 9adc99c305..7b6821d63d 100644
--- a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
@@ -50,6 +50,7 @@ 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.page.PageData;
+import org.thingsboard.server.common.data.page.PageDataIterableByTenant;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.data.rule.DefaultRuleChainCreateRequest;
@@ -70,10 +71,12 @@ import org.thingsboard.server.service.script.RuleNodeJsScriptEngine;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
+import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@Slf4j
@@ -85,7 +88,10 @@ public class RuleChainController extends BaseController {
public static final String RULE_CHAIN_ID = "ruleChainId";
public static final String RULE_NODE_ID = "ruleNodeId";
+ private static final int DEFAULT_PAGE_SIZE = 1000;
+
private static final ObjectMapper objectMapper = new ObjectMapper();
+ public static final int TIMEOUT = 20;
@Autowired
private InstallScripts installScripts;
@@ -388,25 +394,25 @@ public class RuleChainController extends BaseController {
TbMsg inMsg = TbMsg.newMsg(msgType, null, new TbMsgMetaData(metadata), TbMsgDataType.JSON, data);
switch (scriptType) {
case "update":
- output = msgToOutput(engine.executeUpdate(inMsg));
+ output = msgToOutput(engine.executeUpdateAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS));
break;
case "generate":
- output = msgToOutput(engine.executeGenerate(inMsg));
+ output = msgToOutput(engine.executeGenerateAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS));
break;
case "filter":
- boolean result = engine.executeFilter(inMsg);
+ boolean result = engine.executeFilterAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS);
output = Boolean.toString(result);
break;
case "switch":
- Set states = engine.executeSwitch(inMsg);
+ Set states = engine.executeSwitchAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS);
output = objectMapper.writeValueAsString(states);
break;
case "json":
- JsonNode json = engine.executeJson(inMsg);
+ JsonNode json = engine.executeJsonAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS);
output = objectMapper.writeValueAsString(json);
break;
case "string":
- output = engine.executeToString(inMsg);
+ output = engine.executeToStringAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS);
break;
default:
throw new IllegalArgumentException("Unsupported script type: " + scriptType);
@@ -477,7 +483,7 @@ public class RuleChainController extends BaseController {
return objectMapper.writeValueAsString(resultNode);
}
- private JsonNode convertMsgToOut(TbMsg msg) throws Exception{
+ private JsonNode convertMsgToOut(TbMsg msg) throws Exception {
ObjectNode msgData = objectMapper.createObjectNode();
if (!StringUtils.isEmpty(msg.getData())) {
msgData.set("msg", objectMapper.readTree(msg.getData()));
@@ -632,13 +638,20 @@ public class RuleChainController extends BaseController {
}
}
+ // TODO: @voba refactor this - add new config to edge rule chain to set it as auto-assign
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/ruleChain/autoAssignToEdgeRuleChains", method = RequestMethod.GET)
@ResponseBody
public List getAutoAssignToEdgeRuleChains() throws ThingsboardException {
try {
TenantId tenantId = getCurrentUser().getTenantId();
- return checkNotNull(ruleChainService.findAutoAssignToEdgeRuleChainsByTenantId(tenantId)).get();
+ List result = new ArrayList<>();
+ PageDataIterableByTenant autoAssignRuleChainsIterator =
+ new PageDataIterableByTenant<>(ruleChainService::findAutoAssignToEdgeRuleChainsByTenantId, tenantId, DEFAULT_PAGE_SIZE);
+ for (RuleChain ruleChain : autoAssignRuleChainsIterator) {
+ result.add(ruleChain);
+ }
+ return checkNotNull(result);
} catch (Exception e) {
throw handleException(e);
}
diff --git a/application/src/main/java/org/thingsboard/server/controller/TenantController.java b/application/src/main/java/org/thingsboard/server/controller/TenantController.java
index 7621d05908..04d37371e3 100644
--- a/application/src/main/java/org/thingsboard/server/controller/TenantController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/TenantController.java
@@ -95,9 +95,7 @@ public class TenantController extends BaseController {
tenant = checkNotNull(tenantService.saveTenant(tenant));
if (newTenant) {
installScripts.createDefaultRuleChains(tenant.getId());
- if (edgesEnabled) {
- installScripts.createDefaultEdgeRuleChains(tenant.getId());
- }
+ installScripts.createDefaultEdgeRuleChains(tenant.getId());
}
tenantProfileCache.evict(tenant.getId());
tbClusterService.onTenantChange(tenant, null);
diff --git a/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java b/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java
index ed8c0a7a17..906e87d608 100644
--- a/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java
+++ b/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java
@@ -25,9 +25,12 @@ import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.access.AccessDeniedHandler;
-import org.springframework.stereotype.Component;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.client.HttpClientErrorException;
+import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.msg.tools.TbRateLimitsException;
@@ -40,14 +43,15 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
-@Component
@Slf4j
-public class ThingsboardErrorResponseHandler implements AccessDeniedHandler {
+@RestControllerAdvice
+public class ThingsboardErrorResponseHandler extends ResponseEntityExceptionHandler implements AccessDeniedHandler {
@Autowired
private ObjectMapper mapper;
@Override
+ @ExceptionHandler(AccessDeniedException.class)
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException,
ServletException {
@@ -60,6 +64,7 @@ public class ThingsboardErrorResponseHandler implements AccessDeniedHandler {
}
}
+ @ExceptionHandler(Exception.class)
public void handle(Exception exception, HttpServletResponse response) {
log.debug("Processing exception {}", exception.getMessage(), exception);
if (!response.isCommitted()) {
@@ -148,7 +153,7 @@ public class ThingsboardErrorResponseHandler implements AccessDeniedHandler {
private void handleAuthenticationException(AuthenticationException authenticationException, HttpServletResponse response) throws IOException {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
- if (authenticationException instanceof BadCredentialsException) {
+ if (authenticationException instanceof BadCredentialsException || authenticationException instanceof UsernameNotFoundException) {
mapper.writeValue(response.getWriter(), ThingsboardErrorResponse.of("Invalid username or password", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED));
} else if (authenticationException instanceof DisabledException) {
mapper.writeValue(response.getWriter(), ThingsboardErrorResponse.of("User account is not active", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED));
@@ -159,7 +164,7 @@ public class ThingsboardErrorResponseHandler implements AccessDeniedHandler {
} else if (authenticationException instanceof AuthMethodNotSupportedException) {
mapper.writeValue(response.getWriter(), ThingsboardErrorResponse.of(authenticationException.getMessage(), ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED));
} else if (authenticationException instanceof UserPasswordExpiredException) {
- UserPasswordExpiredException expiredException = (UserPasswordExpiredException)authenticationException;
+ UserPasswordExpiredException expiredException = (UserPasswordExpiredException) authenticationException;
String resetToken = expiredException.getResetToken();
mapper.writeValue(response.getWriter(), ThingsboardCredentialsExpiredResponse.of(expiredException.getMessage(), resetToken));
} else {
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java b/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java
index 66e64c14f1..c861207f2d 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java
@@ -15,9 +15,7 @@
*/
package org.thingsboard.server.service.edge;
-import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
@@ -25,45 +23,30 @@ import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
-import org.thingsboard.server.common.data.EdgeUtils;
-import org.thingsboard.server.common.data.EntityType;
-import org.thingsboard.server.common.data.User;
-import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType;
-import org.thingsboard.server.common.data.id.AlarmId;
-import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EdgeId;
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.TenantId;
-import org.thingsboard.server.common.data.page.PageData;
-import org.thingsboard.server.common.data.page.PageLink;
-import org.thingsboard.server.common.data.page.TimePageLink;
-import org.thingsboard.server.common.data.relation.EntityRelation;
-import org.thingsboard.server.common.data.rule.RuleChain;
-import org.thingsboard.server.common.data.rule.RuleChainConnectionInfo;
import org.thingsboard.server.common.msg.queue.TbCallback;
-import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.edge.EdgeEventService;
import org.thingsboard.server.dao.edge.EdgeService;
-import org.thingsboard.server.dao.rule.RuleChainService;
-import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.util.TbCoreComponent;
+import org.thingsboard.server.service.edge.rpc.processor.AlarmEdgeProcessor;
+import org.thingsboard.server.service.edge.rpc.processor.CustomerEdgeProcessor;
+import org.thingsboard.server.service.edge.rpc.processor.EdgeProcessor;
+import org.thingsboard.server.service.edge.rpc.processor.EntityEdgeProcessor;
+import org.thingsboard.server.service.edge.rpc.processor.RelationEdgeProcessor;
import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.queue.TbClusterService;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -73,30 +56,32 @@ import java.util.concurrent.Executors;
@Slf4j
public class DefaultEdgeNotificationService implements EdgeNotificationService {
- private static final ObjectMapper mapper = new ObjectMapper();
+ @Autowired
+ private EdgeService edgeService;
- private static final int DEFAULT_LIMIT = 100;
+ @Autowired
+ private EdgeEventService edgeEventService;
@Autowired
- private EdgeService edgeService;
+ private TbClusterService clusterService;
@Autowired
- private AlarmService alarmService;
+ private DbCallbackExecutorService dbCallbackExecutorService;
@Autowired
- private UserService userService;
+ private EdgeProcessor edgeProcessor;
@Autowired
- private RuleChainService ruleChainService;
+ private EntityEdgeProcessor entityProcessor;
@Autowired
- private EdgeEventService edgeEventService;
+ private AlarmEdgeProcessor alarmProcessor;
@Autowired
- private TbClusterService clusterService;
+ private RelationEdgeProcessor relationProcessor;
@Autowired
- private DbCallbackExecutorService dbCallbackExecutorService;
+ private CustomerEdgeProcessor customerProcessor;
private ExecutorService tsCallBackExecutor;
@@ -115,7 +100,7 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService {
@Override
public Edge setEdgeRootRuleChain(TenantId tenantId, Edge edge, RuleChainId ruleChainId) throws IOException {
edge.setRootRuleChainId(ruleChainId);
- Edge savedEdge = edgeService.saveEdge(edge);
+ Edge savedEdge = edgeService.saveEdge(edge, true);
saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.RULE_CHAIN, EdgeEventActionType.UPDATED, ruleChainId, null);
return savedEdge;
}
@@ -160,7 +145,7 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService {
EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType());
switch (type) {
case EDGE:
- processEdge(tenantId, edgeNotificationMsg);
+ edgeProcessor.processEdgeNotification(tenantId, edgeNotificationMsg);
break;
case USER:
case ASSET:
@@ -169,20 +154,20 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService {
case ENTITY_VIEW:
case DASHBOARD:
case RULE_CHAIN:
- processEntity(tenantId, edgeNotificationMsg);
+ entityProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
break;
case CUSTOMER:
- processCustomer(tenantId, edgeNotificationMsg);
+ customerProcessor.processCustomerNotification(tenantId, edgeNotificationMsg);
break;
case WIDGETS_BUNDLE:
case WIDGET_TYPE:
- processWidgetBundleOrWidgetType(tenantId, edgeNotificationMsg);
+ entityProcessor.processEntityNotificationForAllEdges(tenantId, edgeNotificationMsg);
break;
case ALARM:
- processAlarm(tenantId, edgeNotificationMsg);
+ alarmProcessor.processAlarmNotification(tenantId, edgeNotificationMsg);
break;
case RELATION:
- processRelation(tenantId, edgeNotificationMsg);
+ relationProcessor.processRelationNotification(tenantId, edgeNotificationMsg);
break;
default:
log.debug("Edge event type [{}] is not designed to be pushed to edge", type);
@@ -195,311 +180,6 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService {
}
}
- private void processEdge(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) {
- try {
- EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction());
- EdgeId edgeId = new EdgeId(new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB()));
- ListenableFuture edgeFuture;
- switch (actionType) {
- case ASSIGNED_TO_CUSTOMER:
- CustomerId customerId = mapper.readValue(edgeNotificationMsg.getBody(), CustomerId.class);
- edgeFuture = edgeService.findEdgeByIdAsync(tenantId, edgeId);
- Futures.addCallback(edgeFuture, new FutureCallback() {
- @Override
- public void onSuccess(@Nullable Edge edge) {
- if (edge != null && !customerId.isNullUid()) {
- saveEdgeEvent(edge.getTenantId(), edge.getId(), EdgeEventType.CUSTOMER, EdgeEventActionType.ADDED, customerId, null);
- PageLink pageLink = new PageLink(DEFAULT_LIMIT);
- PageData pageData;
- do {
- pageData = userService.findCustomerUsers(tenantId, customerId, pageLink);
- if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
- log.trace("[{}] [{}] user(s) are going to be added to edge.", edge.getId(), pageData.getData().size());
- for (User user : pageData.getData()) {
- saveEdgeEvent(edge.getTenantId(), edge.getId(), EdgeEventType.USER, EdgeEventActionType.ADDED, user.getId(), null);
- }
- if (pageData.hasNext()) {
- pageLink = pageLink.nextPageLink();
- }
- }
- } while (pageData != null && pageData.hasNext());
- }
- }
-
- @Override
- public void onFailure(Throwable t) {
- log.error("Can't find edge by id [{}]", edgeNotificationMsg, t);
- }
- }, dbCallbackExecutorService);
- break;
- case UNASSIGNED_FROM_CUSTOMER:
- CustomerId customerIdToDelete = mapper.readValue(edgeNotificationMsg.getBody(), CustomerId.class);
- edgeFuture = edgeService.findEdgeByIdAsync(tenantId, edgeId);
- Futures.addCallback(edgeFuture, new FutureCallback() {
- @Override
- public void onSuccess(@Nullable Edge edge) {
- if (edge != null && !customerIdToDelete.isNullUid()) {
- saveEdgeEvent(edge.getTenantId(), edge.getId(), EdgeEventType.CUSTOMER, EdgeEventActionType.DELETED, customerIdToDelete, null);
- }
- }
-
- @Override
- public void onFailure(Throwable t) {
- log.error("Can't find edge by id [{}]", edgeNotificationMsg, t);
- }
- }, dbCallbackExecutorService);
- break;
- }
- } catch (Exception e) {
- log.error("Exception during processing edge event", e);
- }
- }
-
- private void processWidgetBundleOrWidgetType(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) {
- EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction());
- EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType());
- EntityId entityId = EntityIdFactory.getByEdgeEventTypeAndUuid(type, new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB()));
- switch (actionType) {
- case ADDED:
- case UPDATED:
- case DELETED:
- processActionForAllEdges(tenantId, type, actionType, entityId);
- break;
- }
- }
-
- private void processCustomer(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) {
- EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction());
- EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType());
- UUID uuid = new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB());
- CustomerId customerId = new CustomerId(EntityIdFactory.getByEdgeEventTypeAndUuid(type, uuid).getId());
- switch (actionType) {
- case UPDATED:
- PageLink pageLink = new PageLink(DEFAULT_LIMIT);
- PageData pageData;
- do {
- pageData = edgeService.findEdgesByTenantIdAndCustomerId(tenantId, customerId, pageLink);
- if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
- for (Edge edge : pageData.getData()) {
- saveEdgeEvent(tenantId, edge.getId(), type, actionType, customerId, null);
- }
- if (pageData.hasNext()) {
- pageLink = pageLink.nextPageLink();
- }
- }
- } while (pageData != null && pageData.hasNext());
- break;
- case DELETED:
- EdgeId edgeId = new EdgeId(new UUID(edgeNotificationMsg.getEdgeIdMSB(), edgeNotificationMsg.getEdgeIdLSB()));
- saveEdgeEvent(tenantId, edgeId, type, actionType, customerId, null);
- break;
- }
- }
-
- private void processEntity(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) {
- EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction());
- EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType());
- EntityId entityId = EntityIdFactory.getByEdgeEventTypeAndUuid(type,
- new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB()));
- EdgeId edgeId = new EdgeId(new UUID(edgeNotificationMsg.getEdgeIdMSB(), edgeNotificationMsg.getEdgeIdLSB()));
- ListenableFuture> edgeIdsFuture;
- switch (actionType) {
- case ADDED: // used only for USER entity
- case UPDATED:
- case CREDENTIALS_UPDATED:
- edgeIdsFuture = edgeService.findRelatedEdgeIdsByEntityId(tenantId, entityId);
- Futures.addCallback(edgeIdsFuture, new FutureCallback>() {
- @Override
- public void onSuccess(@Nullable List edgeIds) {
- if (edgeIds != null && !edgeIds.isEmpty()) {
- for (EdgeId edgeId : edgeIds) {
- saveEdgeEvent(tenantId, edgeId, type, actionType, entityId, null);
- }
- }
- }
- @Override
- public void onFailure(Throwable throwable) {
- log.error("Failed to find related edge ids [{}]", edgeNotificationMsg, throwable);
- }
- }, dbCallbackExecutorService);
- break;
- case ASSIGNED_TO_CUSTOMER:
- case UNASSIGNED_FROM_CUSTOMER:
- edgeIdsFuture = edgeService.findRelatedEdgeIdsByEntityId(tenantId, entityId);
- Futures.addCallback(edgeIdsFuture, new FutureCallback>() {
- @Override
- public void onSuccess(@Nullable List edgeIds) {
- if (edgeIds != null && !edgeIds.isEmpty()) {
- for (EdgeId edgeId : edgeIds) {
- try {
- CustomerId customerId = mapper.readValue(edgeNotificationMsg.getBody(), CustomerId.class);
- ListenableFuture future = edgeService.findEdgeByIdAsync(tenantId, edgeId);
- Futures.addCallback(future, new FutureCallback() {
- @Override
- public void onSuccess(@Nullable Edge edge) {
- if (edge != null && edge.getCustomerId() != null &&
- !edge.getCustomerId().isNullUid() && edge.getCustomerId().equals(customerId)) {
- saveEdgeEvent(tenantId, edgeId, type, actionType, entityId, null);
- }
- }
- @Override
- public void onFailure(Throwable throwable) {
- log.error("Failed to find edge by id [{}]", edgeNotificationMsg, throwable);
- }
- }, dbCallbackExecutorService);
- } catch (Exception e) {
- log.error("Can't parse customer id from entity body [{}]", edgeNotificationMsg, e);
- }
- }
- }
- }
-
- @Override
- public void onFailure(Throwable throwable) {
- log.error("Failed to find related edge ids [{}]", edgeNotificationMsg, throwable);
- }
- }, dbCallbackExecutorService);
- break;
- case DELETED:
- saveEdgeEvent(tenantId, edgeId, type, actionType, entityId, null);
- break;
- case ASSIGNED_TO_EDGE:
- case UNASSIGNED_FROM_EDGE:
- saveEdgeEvent(tenantId, edgeId, type, actionType, entityId, null);
- if (type.equals(EdgeEventType.RULE_CHAIN)) {
- updateDependentRuleChains(tenantId, new RuleChainId(entityId.getId()), edgeId);
- }
- break;
- }
- }
-
- private void updateDependentRuleChains(TenantId tenantId, RuleChainId processingRuleChainId, EdgeId edgeId) {
- PageLink pageLink = new PageLink(DEFAULT_LIMIT);
- PageData pageData;
- do {
- pageData = ruleChainService.findRuleChainsByTenantIdAndEdgeId(tenantId, edgeId, pageLink);
- if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
- for (RuleChain ruleChain : pageData.getData()) {
- if (!ruleChain.getId().equals(processingRuleChainId)) {
- List connectionInfos =
- ruleChainService.loadRuleChainMetaData(ruleChain.getTenantId(), ruleChain.getId()).getRuleChainConnections();
- if (connectionInfos != null && !connectionInfos.isEmpty()) {
- for (RuleChainConnectionInfo connectionInfo : connectionInfos) {
- if (connectionInfo.getTargetRuleChainId().equals(processingRuleChainId)) {
- saveEdgeEvent(tenantId,
- edgeId,
- EdgeEventType.RULE_CHAIN_METADATA,
- EdgeEventActionType.UPDATED,
- ruleChain.getId(),
- null);
- }
- }
- }
- }
- }
- if (pageData.hasNext()) {
- pageLink = pageLink.nextPageLink();
- }
- }
- } while (pageData != null && pageData.hasNext());
- }
-
- private void processAlarm(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) {
- AlarmId alarmId = new AlarmId(new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB()));
- ListenableFuture alarmFuture = alarmService.findAlarmByIdAsync(tenantId, alarmId);
- Futures.addCallback(alarmFuture, new FutureCallback() {
- @Override
- public void onSuccess(@Nullable Alarm alarm) {
- if (alarm != null) {
- EdgeEventType type = EdgeUtils.getEdgeEventTypeByEntityType(alarm.getOriginator().getEntityType());
- if (type != null) {
- ListenableFuture> relatedEdgeIdsByEntityIdFuture = edgeService.findRelatedEdgeIdsByEntityId(tenantId, alarm.getOriginator());
- Futures.addCallback(relatedEdgeIdsByEntityIdFuture, new FutureCallback>() {
- @Override
- public void onSuccess(@Nullable List relatedEdgeIdsByEntityId) {
- if (relatedEdgeIdsByEntityId != null) {
- for (EdgeId edgeId : relatedEdgeIdsByEntityId) {
- saveEdgeEvent(tenantId,
- edgeId,
- EdgeEventType.ALARM,
- EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()),
- alarmId,
- null);
- }
- }
- }
-
- @Override
- public void onFailure(Throwable t) {
- log.warn("[{}] can't find related edge ids by entity id [{}]", tenantId.getId(), alarm.getOriginator(), t);
- }
- }, dbCallbackExecutorService);
- }
- }
- }
-
- @Override
- public void onFailure(Throwable t) {
- log.warn("[{}] can't find alarm by id [{}]", tenantId.getId(), alarmId.getId(), t);
- }
- }, dbCallbackExecutorService);
- }
-
- private void processRelation(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) throws JsonProcessingException {
- EntityRelation relation = mapper.readValue(edgeNotificationMsg.getBody(), EntityRelation.class);
- if (!relation.getFrom().getEntityType().equals(EntityType.EDGE) &&
- !relation.getTo().getEntityType().equals(EntityType.EDGE)) {
- List>> futures = new ArrayList<>();
- futures.add(edgeService.findRelatedEdgeIdsByEntityId(tenantId, relation.getTo()));
- futures.add(edgeService.findRelatedEdgeIdsByEntityId(tenantId, relation.getFrom()));
- ListenableFuture>> combinedFuture = Futures.allAsList(futures);
- Futures.addCallback(combinedFuture, new FutureCallback>>() {
- @Override
- public void onSuccess(@Nullable List> listOfListsEdgeIds) {
- Set uniqueEdgeIds = new HashSet<>();
- if (listOfListsEdgeIds != null && !listOfListsEdgeIds.isEmpty()) {
- for (List listOfListsEdgeId : listOfListsEdgeIds) {
- if (listOfListsEdgeId != null) {
- uniqueEdgeIds.addAll(listOfListsEdgeId);
- }
- }
- }
- if (!uniqueEdgeIds.isEmpty()) {
- for (EdgeId edgeId : uniqueEdgeIds) {
- saveEdgeEvent(tenantId,
- edgeId,
- EdgeEventType.RELATION,
- EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()),
- null,
- mapper.valueToTree(relation));
- }
- }
- }
-
- @Override
- public void onFailure(Throwable t) {
- log.warn("[{}] can't find related edge ids by relation to id [{}] and relation from id [{}]" ,
- tenantId.getId(), relation.getTo().getId(), relation.getFrom().getId(), t);
- }
- }, dbCallbackExecutorService);
- }
- }
-
- private void processActionForAllEdges(TenantId tenantId, EdgeEventType type, EdgeEventActionType actionType, EntityId entityId) {
- PageLink pageLink = new PageLink(DEFAULT_LIMIT);
- PageData pageData;
- do {
- pageData = edgeService.findEdgesByTenantId(tenantId, pageLink);
- if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
- for (Edge edge : pageData.getData()) {
- saveEdgeEvent(tenantId, edge.getId(), type, actionType, entityId, null);
- }
- if (pageData.hasNext()) {
- pageLink = pageLink.nextPageLink();
- }
- }
- } while (pageData != null && pageData.hasNext());
- }
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java b/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java
index eb3852f7ac..de9d2d142a 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java
@@ -16,218 +16,130 @@
package org.thingsboard.server.service.edge;
import lombok.Data;
-import lombok.Getter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
-import org.thingsboard.server.actors.service.ActorService;
-import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService;
-import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
-import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.dao.device.DeviceProfileService;
-import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.edge.EdgeEventService;
import org.thingsboard.server.dao.edge.EdgeService;
-import org.thingsboard.server.dao.entityview.EntityViewService;
-import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService;
+import org.thingsboard.server.dao.settings.AdminSettingsService;
import org.thingsboard.server.dao.user.UserService;
-import org.thingsboard.server.dao.widget.WidgetTypeService;
import org.thingsboard.server.dao.widget.WidgetsBundleService;
-import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.edge.rpc.EdgeEventStorageSettings;
-import org.thingsboard.server.service.edge.rpc.constructor.AdminSettingsMsgConstructor;
-import org.thingsboard.server.service.edge.rpc.constructor.AlarmMsgConstructor;
-import org.thingsboard.server.service.edge.rpc.constructor.AssetMsgConstructor;
-import org.thingsboard.server.service.edge.rpc.constructor.CustomerMsgConstructor;
-import org.thingsboard.server.service.edge.rpc.constructor.DashboardMsgConstructor;
-import org.thingsboard.server.service.edge.rpc.constructor.DeviceMsgConstructor;
-import org.thingsboard.server.service.edge.rpc.constructor.DeviceProfileMsgConstructor;
-import org.thingsboard.server.service.edge.rpc.constructor.EntityDataMsgConstructor;
-import org.thingsboard.server.service.edge.rpc.constructor.EntityViewMsgConstructor;
-import org.thingsboard.server.service.edge.rpc.constructor.RelationMsgConstructor;
-import org.thingsboard.server.service.edge.rpc.constructor.RuleChainMsgConstructor;
-import org.thingsboard.server.service.edge.rpc.constructor.UserMsgConstructor;
-import org.thingsboard.server.service.edge.rpc.constructor.WidgetTypeMsgConstructor;
-import org.thingsboard.server.service.edge.rpc.constructor.WidgetsBundleMsgConstructor;
-import org.thingsboard.server.service.edge.rpc.init.SyncEdgeService;
-import org.thingsboard.server.service.edge.rpc.processor.AlarmProcessor;
-import org.thingsboard.server.service.edge.rpc.processor.DeviceProcessor;
-import org.thingsboard.server.service.edge.rpc.processor.RelationProcessor;
-import org.thingsboard.server.service.edge.rpc.processor.TelemetryProcessor;
+import org.thingsboard.server.service.edge.rpc.processor.AdminSettingsEdgeProcessor;
+import org.thingsboard.server.service.edge.rpc.processor.AlarmEdgeProcessor;
+import org.thingsboard.server.service.edge.rpc.processor.AssetEdgeProcessor;
+import org.thingsboard.server.service.edge.rpc.processor.CustomerEdgeProcessor;
+import org.thingsboard.server.service.edge.rpc.processor.DashboardEdgeProcessor;
+import org.thingsboard.server.service.edge.rpc.processor.DeviceEdgeProcessor;
+import org.thingsboard.server.service.edge.rpc.processor.DeviceProfileEdgeProcessor;
+import org.thingsboard.server.service.edge.rpc.processor.EntityEdgeProcessor;
+import org.thingsboard.server.service.edge.rpc.processor.EntityViewEdgeProcessor;
+import org.thingsboard.server.service.edge.rpc.processor.RelationEdgeProcessor;
+import org.thingsboard.server.service.edge.rpc.processor.RuleChainEdgeProcessor;
+import org.thingsboard.server.service.edge.rpc.processor.TelemetryEdgeProcessor;
+import org.thingsboard.server.service.edge.rpc.processor.UserEdgeProcessor;
+import org.thingsboard.server.service.edge.rpc.processor.WidgetBundleEdgeProcessor;
+import org.thingsboard.server.service.edge.rpc.processor.WidgetTypeEdgeProcessor;
+import org.thingsboard.server.service.edge.rpc.sync.EdgeRequestsService;
import org.thingsboard.server.service.executors.DbCallbackExecutorService;
-import org.thingsboard.server.service.queue.TbClusterService;
-import org.thingsboard.server.service.state.DeviceStateService;
+import org.thingsboard.server.service.executors.GrpcCallbackExecutorService;
@Component
@TbCoreComponent
@Data
+@Lazy
public class EdgeContextComponent {
- @Lazy
@Autowired
private EdgeService edgeService;
- @Autowired
- private PartitionService partitionService;
-
- @Lazy
@Autowired
private EdgeEventService edgeEventService;
- @Lazy
@Autowired
- private AssetService assetService;
+ private AdminSettingsService adminSettingsService;
- @Lazy
@Autowired
- private DeviceService deviceService;
+ private AssetService assetService;
- @Lazy
@Autowired
private DeviceProfileService deviceProfileService;
- @Lazy
- @Autowired
- private DeviceCredentialsService deviceCredentialsService;
-
- @Lazy
- @Autowired
- private EntityViewService entityViewService;
-
- @Lazy
@Autowired
private AttributesService attributesService;
- @Lazy
- @Autowired
- private CustomerService customerService;
-
- @Lazy
- @Autowired
- private RelationService relationService;
-
- @Lazy
- @Autowired
- private AlarmService alarmService;
-
- @Lazy
@Autowired
private DashboardService dashboardService;
- @Lazy
@Autowired
private RuleChainService ruleChainService;
- @Lazy
@Autowired
private UserService userService;
- @Lazy
- @Autowired
- private ActorService actorService;
-
- @Lazy
@Autowired
private WidgetsBundleService widgetsBundleService;
- @Lazy
- @Autowired
- private WidgetTypeService widgetTypeService;
-
- @Lazy
- @Autowired
- private DeviceStateService deviceStateService;
-
- @Lazy
- @Autowired
- private TbClusterService tbClusterService;
-
- @Lazy
@Autowired
- private SyncEdgeService syncEdgeService;
+ private EdgeRequestsService edgeRequestsService;
- @Lazy
@Autowired
- private RuleChainMsgConstructor ruleChainMsgConstructor;
+ private AlarmEdgeProcessor alarmProcessor;
- @Lazy
@Autowired
- private AlarmMsgConstructor alarmMsgConstructor;
+ private DeviceProfileEdgeProcessor deviceProfileProcessor;
- @Lazy
@Autowired
- private DeviceMsgConstructor deviceMsgConstructor;
+ private DeviceEdgeProcessor deviceProcessor;
- @Lazy
@Autowired
- private DeviceProfileMsgConstructor deviceProfileMsgConstructor;
+ private EntityEdgeProcessor entityProcessor;
- @Lazy
@Autowired
- private AssetMsgConstructor assetMsgConstructor;
+ private AssetEdgeProcessor assetProcessor;
- @Lazy
@Autowired
- private EntityViewMsgConstructor entityViewMsgConstructor;
+ private EntityViewEdgeProcessor entityViewProcessor;
- @Lazy
@Autowired
- private DashboardMsgConstructor dashboardMsgConstructor;
+ private UserEdgeProcessor userProcessor;
- @Lazy
@Autowired
- private CustomerMsgConstructor customerMsgConstructor;
+ private RelationEdgeProcessor relationProcessor;
- @Lazy
@Autowired
- private UserMsgConstructor userMsgConstructor;
+ private TelemetryEdgeProcessor telemetryProcessor;
- @Lazy
@Autowired
- private RelationMsgConstructor relationMsgConstructor;
+ private DashboardEdgeProcessor dashboardProcessor;
- @Lazy
@Autowired
- private WidgetsBundleMsgConstructor widgetsBundleMsgConstructor;
+ private RuleChainEdgeProcessor ruleChainProcessor;
- @Lazy
@Autowired
- private WidgetTypeMsgConstructor widgetTypeMsgConstructor;
+ private CustomerEdgeProcessor customerProcessor;
- @Lazy
@Autowired
- private AdminSettingsMsgConstructor adminSettingsMsgConstructor;
+ private WidgetBundleEdgeProcessor widgetBundleProcessor;
- @Lazy
@Autowired
- private EntityDataMsgConstructor entityDataMsgConstructor;
+ private WidgetTypeEdgeProcessor widgetTypeProcessor;
- @Lazy
@Autowired
- private AlarmProcessor alarmProcessor;
+ private AdminSettingsEdgeProcessor adminSettingsProcessor;
- @Lazy
- @Autowired
- private DeviceProcessor deviceProcessor;
-
- @Lazy
- @Autowired
- private RelationProcessor relationProcessor;
-
- @Lazy
- @Autowired
- private TelemetryProcessor telemetryProcessor;
-
- @Lazy
@Autowired
private EdgeEventStorageSettings edgeEventStorageSettings;
@Autowired
- @Getter
private DbCallbackExecutorService dbCallbackExecutor;
+
+ @Autowired
+ private GrpcCallbackExecutorService grpcCallbackExecutorService;
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeEventUtils.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeEventUtils.java
new file mode 100644
index 0000000000..f09184504e
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeEventUtils.java
@@ -0,0 +1,48 @@
+/**
+ * Copyright © 2016-2021 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.edge.rpc;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.thingsboard.server.common.data.edge.EdgeEvent;
+import org.thingsboard.server.common.data.edge.EdgeEventActionType;
+import org.thingsboard.server.common.data.edge.EdgeEventType;
+import org.thingsboard.server.common.data.id.EdgeId;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.TenantId;
+
+public final class EdgeEventUtils {
+
+ private EdgeEventUtils() {
+ }
+
+ public static EdgeEvent constructEdgeEvent(TenantId tenantId,
+ EdgeId edgeId,
+ EdgeEventType type,
+ EdgeEventActionType action,
+ EntityId entityId,
+ JsonNode body) {
+ EdgeEvent edgeEvent = new EdgeEvent();
+ edgeEvent.setTenantId(tenantId);
+ edgeEvent.setEdgeId(edgeId);
+ edgeEvent.setType(type);
+ edgeEvent.setAction(action);
+ if (entityId != null) {
+ edgeEvent.setEntityId(entityId.getId());
+ }
+ edgeEvent.setBody(body);
+ return edgeEvent;
+ }
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java
index ab0857f863..e118a0b0c2 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java
@@ -16,8 +16,8 @@
package org.thingsboard.server.service.edge.rpc;
import com.fasterxml.jackson.databind.ObjectMapper;
-import com.google.common.io.Resources;
import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
import io.grpc.Server;
import io.grpc.netty.NettyServerBuilder;
import io.grpc.stub.StreamObserver;
@@ -28,16 +28,16 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.DataConstants;
-import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.ResourceUtils;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry;
-import org.thingsboard.server.gen.edge.EdgeRpcServiceGrpc;
-import org.thingsboard.server.gen.edge.RequestMsg;
-import org.thingsboard.server.gen.edge.ResponseMsg;
+import org.thingsboard.server.gen.edge.v1.EdgeRpcServiceGrpc;
+import org.thingsboard.server.gen.edge.v1.RequestMsg;
+import org.thingsboard.server.gen.edge.v1.ResponseMsg;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.edge.EdgeContextComponent;
import org.thingsboard.server.service.state.DefaultDeviceStateService;
@@ -46,9 +46,10 @@ import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
-import java.io.File;
import java.io.IOException;
+import java.io.InputStream;
import java.util.Collections;
+import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
@@ -57,6 +58,8 @@ import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
@Service
@Slf4j
@@ -65,7 +68,8 @@ import java.util.concurrent.TimeUnit;
public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase implements EdgeRpcService {
private final ConcurrentMap sessions = new ConcurrentHashMap<>();
- private final ConcurrentMap sessionNewEvents = new ConcurrentHashMap<>();
+ private final ConcurrentMap sessionNewEventsLocks = new ConcurrentHashMap<>();
+ private final Map sessionNewEvents = new HashMap<>();
private final ConcurrentMap> sessionEdgeEventChecks = new ConcurrentHashMap<>();
private static final ObjectMapper mapper = new ObjectMapper();
@@ -81,10 +85,15 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i
private boolean persistToTelemetry;
@Value("${edges.rpc.client_max_keep_alive_time_sec}")
private int clientMaxKeepAliveTimeSec;
+ @Value("${edges.rpc.max_inbound_message_size:4194304}")
+ private int maxInboundMessageSize;
@Value("${edges.scheduler_pool_size}")
private int schedulerPoolSize;
+ @Value("${edges.send_scheduler_pool_size}")
+ private int sendSchedulerPoolSize;
+
@Autowired
private EdgeContextComponent ctx;
@@ -93,19 +102,22 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i
private Server server;
- private ScheduledExecutorService scheduler;
+ private ScheduledExecutorService edgeEventProcessingExecutorService;
+
+ private ScheduledExecutorService sendDownlinkExecutorService;
@PostConstruct
public void init() {
log.info("Initializing Edge RPC service!");
NettyServerBuilder builder = NettyServerBuilder.forPort(rpcPort)
.permitKeepAliveTime(clientMaxKeepAliveTimeSec, TimeUnit.SECONDS)
+ .maxInboundMessageSize(maxInboundMessageSize)
.addService(this);
if (sslEnabled) {
try {
- File certFile = new File(Resources.getResource(certFileResource).toURI());
- File privateKeyFile = new File(Resources.getResource(privateKeyResource).toURI());
- builder.useTransportSecurity(certFile, privateKeyFile);
+ InputStream certFileIs = ResourceUtils.getInputStream(this, certFileResource);
+ InputStream privateKeyFileIs = ResourceUtils.getInputStream(this, privateKeyResource);
+ builder.useTransportSecurity(certFileIs, privateKeyFileIs);
} catch (Exception e) {
log.error("Unable to set up SSL context. Reason: " + e.getMessage(), e);
throw new RuntimeException("Unable to set up SSL context!", e);
@@ -119,7 +131,8 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i
log.error("Failed to start Edge RPC server!", e);
throw new RuntimeException("Failed to start Edge RPC server!");
}
- this.scheduler = Executors.newScheduledThreadPool(schedulerPoolSize, ThingsBoardThreadFactory.forName("edge-scheduler"));
+ this.edgeEventProcessingExecutorService = Executors.newScheduledThreadPool(schedulerPoolSize, ThingsBoardThreadFactory.forName("edge-scheduler"));
+ this.sendDownlinkExecutorService = Executors.newScheduledThreadPool(sendSchedulerPoolSize, ThingsBoardThreadFactory.forName("edge-send-scheduler"));
log.info("Edge RPC service initialized!");
}
@@ -136,62 +149,84 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i
sessionEdgeEventChecks.remove(edgeId);
}
}
- if (scheduler != null) {
- scheduler.shutdownNow();
+ if (edgeEventProcessingExecutorService != null) {
+ edgeEventProcessingExecutorService.shutdownNow();
+ }
+ if (sendDownlinkExecutorService != null) {
+ sendDownlinkExecutorService.shutdownNow();
}
}
@Override
public StreamObserver handleMsgs(StreamObserver outputStream) {
- return new EdgeGrpcSession(ctx, outputStream, this::onEdgeConnect, this::onEdgeDisconnect, mapper).getInputStream();
+ return new EdgeGrpcSession(ctx, outputStream, this::onEdgeConnect, this::onEdgeDisconnect, mapper, sendDownlinkExecutorService).getInputStream();
}
@Override
- public void updateEdge(Edge edge) {
+ public void updateEdge(TenantId tenantId, Edge edge) {
EdgeGrpcSession session = sessions.get(edge.getId());
if (session != null && session.isConnected()) {
- log.debug("[{}] Updating configuration for edge [{}] [{}]", edge.getTenantId(), edge.getName(), edge.getId());
+ log.debug("[{}] Updating configuration for edge [{}] [{}]", tenantId, edge.getName(), edge.getId());
session.onConfigurationUpdate(edge);
} else {
- log.debug("[{}] Session doesn't exist for edge [{}] [{}]", edge.getTenantId(), edge.getName(), edge.getId());
+ log.debug("[{}] Session doesn't exist for edge [{}] [{}]", tenantId, edge.getName(), edge.getId());
}
}
@Override
- public void deleteEdge(EdgeId edgeId) {
+ public void deleteEdge(TenantId tenantId, EdgeId edgeId) {
EdgeGrpcSession session = sessions.get(edgeId);
if (session != null && session.isConnected()) {
- log.info("Closing and removing session for edge [{}]", edgeId);
+ log.info("[{}] Closing and removing session for edge [{}]", tenantId, edgeId);
session.close();
sessions.remove(edgeId);
- sessionNewEvents.remove(edgeId);
+ final Lock newEventLock = sessionNewEventsLocks.computeIfAbsent(edgeId, id -> new ReentrantLock());
+ newEventLock.lock();
+ try {
+ sessionNewEvents.remove(edgeId);
+ } finally {
+ newEventLock.unlock();
+ }
cancelScheduleEdgeEventsCheck(edgeId);
}
}
@Override
- public void onEdgeEvent(EdgeId edgeId) {
- log.trace("[{}] onEdgeEvent", edgeId.getId());
- if (Boolean.FALSE.equals(sessionNewEvents.get(edgeId))) {
- log.trace("[{}] set session new events flag to true", edgeId.getId());
- sessionNewEvents.put(edgeId, true);
+ public void onEdgeEvent(TenantId tenantId, EdgeId edgeId) {
+ log.trace("[{}] onEdgeEvent [{}]", tenantId, edgeId.getId());
+ final Lock newEventLock = sessionNewEventsLocks.computeIfAbsent(edgeId, id -> new ReentrantLock());
+ newEventLock.lock();
+ try {
+ if (Boolean.FALSE.equals(sessionNewEvents.get(edgeId))) {
+ log.trace("[{}] set session new events flag to true [{}]", tenantId, edgeId.getId());
+ sessionNewEvents.put(edgeId, true);
+ }
+ } finally {
+ newEventLock.unlock();
}
}
private void onEdgeConnect(EdgeId edgeId, EdgeGrpcSession edgeGrpcSession) {
log.info("[{}] edge [{}] connected successfully.", edgeGrpcSession.getSessionId(), edgeId);
sessions.put(edgeId, edgeGrpcSession);
- sessionNewEvents.put(edgeId, false);
+ final Lock newEventLock = sessionNewEventsLocks.computeIfAbsent(edgeId, id -> new ReentrantLock());
+ newEventLock.lock();
+ try {
+ sessionNewEvents.put(edgeId, true);
+ } finally {
+ newEventLock.unlock();
+ }
save(edgeId, DefaultDeviceStateService.ACTIVITY_STATE, true);
save(edgeId, DefaultDeviceStateService.LAST_CONNECT_TIME, System.currentTimeMillis());
cancelScheduleEdgeEventsCheck(edgeId);
scheduleEdgeEventsCheck(edgeGrpcSession);
}
- public EdgeGrpcSession getEdgeGrpcSessionById(TenantId tenantId, EdgeId edgeId) {
+ @Override
+ public void startSyncProcess(TenantId tenantId, EdgeId edgeId) {
EdgeGrpcSession session = sessions.get(edgeId);
if (session != null && session.isConnected()) {
- return session;
+ session.startSyncProcess(tenantId, edgeId);
} else {
log.error("[{}] Edge is not connected [{}]", tenantId, edgeId);
throw new RuntimeException("Edge is not connected");
@@ -202,19 +237,37 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i
EdgeId edgeId = session.getEdge().getId();
UUID tenantId = session.getEdge().getTenantId().getId();
if (sessions.containsKey(edgeId)) {
- ScheduledFuture> schedule = scheduler.schedule(() -> {
+ ScheduledFuture> edgeEventCheckTask = edgeEventProcessingExecutorService.schedule(() -> {
try {
- if (Boolean.TRUE.equals(sessionNewEvents.get(edgeId))) {
- log.trace("[{}] Set session new events flag to false", edgeId.getId());
- sessionNewEvents.put(edgeId, false);
- session.processEdgeEvents();
+ final Lock newEventLock = sessionNewEventsLocks.computeIfAbsent(edgeId, id -> new ReentrantLock());
+ newEventLock.lock();
+ try {
+ if (Boolean.TRUE.equals(sessionNewEvents.get(edgeId))) {
+ log.trace("[{}] Set session new events flag to false", edgeId.getId());
+ sessionNewEvents.put(edgeId, false);
+ Futures.addCallback(session.processEdgeEvents(), new FutureCallback<>() {
+ @Override
+ public void onSuccess(Void result) {
+ scheduleEdgeEventsCheck(session);
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ log.warn("[{}] Failed to process edge events for edge [{}]!", tenantId, session.getEdge().getId().getId(), t);
+ scheduleEdgeEventsCheck(session);
+ }
+ }, ctx.getGrpcCallbackExecutorService());
+ } else {
+ scheduleEdgeEventsCheck(session);
+ }
+ } finally {
+ newEventLock.unlock();
}
} catch (Exception e) {
log.warn("[{}] Failed to process edge events for edge [{}]!", tenantId, session.getEdge().getId().getId(), e);
}
- scheduleEdgeEventsCheck(session);
}, ctx.getEdgeEventStorageSettings().getNoRecordsSleepInterval(), TimeUnit.MILLISECONDS);
- sessionEdgeEventChecks.put(edgeId, schedule);
+ sessionEdgeEventChecks.put(edgeId, edgeEventCheckTask);
log.trace("[{}] Check edge event scheduled for edge [{}]", tenantId, edgeId.getId());
} else {
log.debug("[{}] Session was removed and edge event check schedule must not be started [{}]",
@@ -236,7 +289,13 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i
private void onEdgeDisconnect(EdgeId edgeId) {
log.info("[{}] edge disconnected!", edgeId);
sessions.remove(edgeId);
- sessionNewEvents.remove(edgeId);
+ final Lock newEventLock = sessionNewEventsLocks.computeIfAbsent(edgeId, id -> new ReentrantLock());
+ newEventLock.lock();
+ try {
+ sessionNewEvents.remove(edgeId);
+ } finally {
+ newEventLock.unlock();
+ }
save(edgeId, DefaultDeviceStateService.ACTIVITY_STATE, false);
save(edgeId, DefaultDeviceStateService.LAST_DISCONNECT_TIME, System.currentTimeMillis());
cancelScheduleEdgeEventsCheck(edgeId);
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java
index fb01926e2a..32e23fddad 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java
@@ -16,111 +16,76 @@
package org.thingsboard.server.service.edge.rpc;
import com.datastax.oss.driver.api.core.uuid.Uuids;
-import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.MoreExecutors;
-import com.google.gson.JsonElement;
+import com.google.common.util.concurrent.SettableFuture;
import io.grpc.stub.StreamObserver;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.Nullable;
-import org.thingsboard.server.common.data.AdminSettings;
-import org.thingsboard.server.common.data.Customer;
-import org.thingsboard.server.common.data.Dashboard;
+import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.DataConstants;
-import org.thingsboard.server.common.data.Device;
-import org.thingsboard.server.common.data.DeviceProfile;
-import org.thingsboard.server.common.data.EntityView;
-import org.thingsboard.server.common.data.HasCustomerId;
-import org.thingsboard.server.common.data.User;
-import org.thingsboard.server.common.data.alarm.Alarm;
-import org.thingsboard.server.common.data.asset.Asset;
+import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
-import org.thingsboard.server.common.data.edge.EdgeEventType;
-import org.thingsboard.server.common.data.id.AlarmId;
-import org.thingsboard.server.common.data.id.AssetId;
-import org.thingsboard.server.common.data.id.CustomerId;
-import org.thingsboard.server.common.data.id.DashboardId;
-import org.thingsboard.server.common.data.id.DeviceId;
-import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EdgeId;
-import org.thingsboard.server.common.data.id.EntityId;
-import org.thingsboard.server.common.data.id.EntityViewId;
-import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
-import org.thingsboard.server.common.data.id.UserId;
-import org.thingsboard.server.common.data.id.WidgetTypeId;
-import org.thingsboard.server.common.data.id.WidgetsBundleId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.page.PageData;
-import org.thingsboard.server.common.data.page.SortOrder;
-import org.thingsboard.server.common.data.page.TimePageLink;
-import org.thingsboard.server.common.data.relation.EntityRelation;
-import org.thingsboard.server.common.data.rule.RuleChain;
-import org.thingsboard.server.common.data.rule.RuleChainMetaData;
-import org.thingsboard.server.common.data.security.DeviceCredentials;
-import org.thingsboard.server.common.data.security.UserCredentials;
-import org.thingsboard.server.common.data.widget.WidgetType;
-import org.thingsboard.server.common.data.widget.WidgetsBundle;
-import org.thingsboard.server.common.transport.util.JsonUtils;
-import org.thingsboard.common.util.JacksonUtil;
-import org.thingsboard.server.gen.edge.AdminSettingsUpdateMsg;
-import org.thingsboard.server.gen.edge.AlarmUpdateMsg;
-import org.thingsboard.server.gen.edge.AssetUpdateMsg;
-import org.thingsboard.server.gen.edge.AttributesRequestMsg;
-import org.thingsboard.server.gen.edge.ConnectRequestMsg;
-import org.thingsboard.server.gen.edge.ConnectResponseCode;
-import org.thingsboard.server.gen.edge.ConnectResponseMsg;
-import org.thingsboard.server.gen.edge.CustomerUpdateMsg;
-import org.thingsboard.server.gen.edge.DashboardUpdateMsg;
-import org.thingsboard.server.gen.edge.DeviceCredentialsRequestMsg;
-import org.thingsboard.server.gen.edge.DeviceCredentialsUpdateMsg;
-import org.thingsboard.server.gen.edge.DeviceProfileUpdateMsg;
-import org.thingsboard.server.gen.edge.DeviceRpcCallMsg;
-import org.thingsboard.server.gen.edge.DeviceUpdateMsg;
-import org.thingsboard.server.gen.edge.DownlinkMsg;
-import org.thingsboard.server.gen.edge.DownlinkResponseMsg;
-import org.thingsboard.server.gen.edge.EdgeConfiguration;
-import org.thingsboard.server.gen.edge.EdgeUpdateMsg;
-import org.thingsboard.server.gen.edge.EntityDataProto;
-import org.thingsboard.server.gen.edge.EntityViewUpdateMsg;
-import org.thingsboard.server.gen.edge.RelationRequestMsg;
-import org.thingsboard.server.gen.edge.RelationUpdateMsg;
-import org.thingsboard.server.gen.edge.RequestMsg;
-import org.thingsboard.server.gen.edge.RequestMsgType;
-import org.thingsboard.server.gen.edge.ResponseMsg;
-import org.thingsboard.server.gen.edge.RuleChainMetadataRequestMsg;
-import org.thingsboard.server.gen.edge.RuleChainMetadataUpdateMsg;
-import org.thingsboard.server.gen.edge.RuleChainUpdateMsg;
-import org.thingsboard.server.gen.edge.UpdateMsgType;
-import org.thingsboard.server.gen.edge.UplinkMsg;
-import org.thingsboard.server.gen.edge.UplinkResponseMsg;
-import org.thingsboard.server.gen.edge.UserCredentialsRequestMsg;
-import org.thingsboard.server.gen.edge.UserCredentialsUpdateMsg;
-import org.thingsboard.server.gen.edge.WidgetTypeUpdateMsg;
-import org.thingsboard.server.gen.edge.WidgetsBundleUpdateMsg;
+import org.thingsboard.server.common.data.page.PageLink;
+import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg;
+import org.thingsboard.server.gen.edge.v1.AttributesRequestMsg;
+import org.thingsboard.server.gen.edge.v1.ConnectRequestMsg;
+import org.thingsboard.server.gen.edge.v1.ConnectResponseCode;
+import org.thingsboard.server.gen.edge.v1.ConnectResponseMsg;
+import org.thingsboard.server.gen.edge.v1.DeviceCredentialsRequestMsg;
+import org.thingsboard.server.gen.edge.v1.DeviceCredentialsUpdateMsg;
+import org.thingsboard.server.gen.edge.v1.DeviceProfileDevicesRequestMsg;
+import org.thingsboard.server.gen.edge.v1.DeviceRpcCallMsg;
+import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg;
+import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
+import org.thingsboard.server.gen.edge.v1.DownlinkResponseMsg;
+import org.thingsboard.server.gen.edge.v1.EdgeConfiguration;
+import org.thingsboard.server.gen.edge.v1.EdgeUpdateMsg;
+import org.thingsboard.server.gen.edge.v1.EntityDataProto;
+import org.thingsboard.server.gen.edge.v1.EntityViewsRequestMsg;
+import org.thingsboard.server.gen.edge.v1.RelationRequestMsg;
+import org.thingsboard.server.gen.edge.v1.RelationUpdateMsg;
+import org.thingsboard.server.gen.edge.v1.RequestMsg;
+import org.thingsboard.server.gen.edge.v1.RequestMsgType;
+import org.thingsboard.server.gen.edge.v1.ResponseMsg;
+import org.thingsboard.server.gen.edge.v1.RuleChainMetadataRequestMsg;
+import org.thingsboard.server.gen.edge.v1.SyncCompletedMsg;
+import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
+import org.thingsboard.server.gen.edge.v1.UplinkMsg;
+import org.thingsboard.server.gen.edge.v1.UplinkResponseMsg;
+import org.thingsboard.server.gen.edge.v1.UserCredentialsRequestMsg;
+import org.thingsboard.server.gen.edge.v1.WidgetBundleTypesRequestMsg;
import org.thingsboard.server.service.edge.EdgeContextComponent;
+import org.thingsboard.server.service.edge.rpc.fetch.EdgeEventFetcher;
+import org.thingsboard.server.service.edge.rpc.fetch.GeneralEdgeEventFetcher;
import java.io.Closeable;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
+import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
+import java.util.stream.Collectors;
@Slf4j
@Data
@@ -135,27 +100,31 @@ public final class EdgeGrpcSession implements Closeable {
private final Consumer sessionCloseListener;
private final ObjectMapper mapper;
+ private final EdgeSessionState sessionState = new EdgeSessionState();
+
private EdgeContextComponent ctx;
private Edge edge;
private StreamObserver inputStream;
private StreamObserver outputStream;
private boolean connected;
+ private boolean syncCompleted;
- private CountDownLatch latch;
+ private ScheduledExecutorService sendDownlinkExecutorService;
EdgeGrpcSession(EdgeContextComponent ctx, StreamObserver outputStream, BiConsumer sessionOpenListener,
- Consumer sessionCloseListener, ObjectMapper mapper) {
+ Consumer sessionCloseListener, ObjectMapper mapper, ScheduledExecutorService sendDownlinkExecutorService) {
this.sessionId = UUID.randomUUID();
this.ctx = ctx;
this.outputStream = outputStream;
this.sessionOpenListener = sessionOpenListener;
this.sessionCloseListener = sessionCloseListener;
this.mapper = mapper;
+ this.sendDownlinkExecutorService = sendDownlinkExecutorService;
initInputStream();
}
private void initInputStream() {
- this.inputStream = new StreamObserver() {
+ this.inputStream = new StreamObserver<>() {
@Override
public void onNext(RequestMsg requestMsg) {
if (!connected && requestMsg.getMsgType().equals(RequestMsgType.CONNECT_RPC_MESSAGE)) {
@@ -169,15 +138,21 @@ public final class EdgeGrpcSession implements Closeable {
connected = true;
}
}
- if (connected && requestMsg.getMsgType().equals(RequestMsgType.SYNC_REQUEST_RPC_MESSAGE)) {
- ctx.getSyncEdgeService().sync(edge.getTenantId(), edge);
- }
if (connected) {
- if (requestMsg.getMsgType().equals(RequestMsgType.UPLINK_RPC_MESSAGE) && requestMsg.hasUplinkMsg()) {
- onUplinkMsg(requestMsg.getUplinkMsg());
+ if (requestMsg.getMsgType().equals(RequestMsgType.SYNC_REQUEST_RPC_MESSAGE)) {
+ if (requestMsg.hasSyncRequestMsg() && requestMsg.getSyncRequestMsg().getSyncRequired()) {
+ startSyncProcess(edge.getTenantId(), edge.getId());
+ } else {
+ syncCompleted = true;
+ }
}
- if (requestMsg.getMsgType().equals(RequestMsgType.UPLINK_RPC_MESSAGE) && requestMsg.hasDownlinkResponseMsg()) {
- onDownlinkResponse(requestMsg.getDownlinkResponseMsg());
+ if (requestMsg.getMsgType().equals(RequestMsgType.UPLINK_RPC_MESSAGE)) {
+ if (requestMsg.hasUplinkMsg()) {
+ onUplinkMsg(requestMsg.getUplinkMsg());
+ }
+ if (requestMsg.hasDownlinkResponseMsg()) {
+ onDownlinkResponse(requestMsg.getDownlinkResponseMsg());
+ }
}
}
}
@@ -198,21 +173,65 @@ public final class EdgeGrpcSession implements Closeable {
if (edge != null) {
try {
sessionCloseListener.accept(edge.getId());
- } catch (Exception ignored) {}
+ } catch (Exception ignored) {
+ }
}
try {
outputStream.onCompleted();
- } catch (Exception ignored) {}
+ } catch (Exception ignored) {
+ }
}
};
}
+ public void startSyncProcess(TenantId tenantId, EdgeId edgeId) {
+ log.trace("[{}][{}] Staring edge sync process", tenantId, edgeId);
+ syncCompleted = false;
+ doSync(new EdgeSyncCursor(ctx, edge));
+ }
+
+ private void doSync(EdgeSyncCursor cursor) {
+ if (cursor.hasNext()) {
+ log.info("[{}][{}] starting sync process, cursor current idx = {}", edge.getTenantId(), edge.getId(), cursor.getCurrentIdx());
+ ListenableFuture uuidListenableFuture = startProcessingEdgeEvents(cursor.getNext());
+ Futures.addCallback(uuidListenableFuture, new FutureCallback<>() {
+ @Override
+ public void onSuccess(@Nullable UUID result) {
+ doSync(cursor);
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ log.error("[{}][{}] Exception during sync process", edge.getTenantId(), edge.getId(), t);
+ }
+ }, ctx.getGrpcCallbackExecutorService());
+ } else {
+ DownlinkMsg syncCompleteDownlinkMsg = DownlinkMsg.newBuilder()
+ .setDownlinkMsgId(EdgeUtils.nextPositiveInt())
+ .setSyncCompletedMsg(SyncCompletedMsg.newBuilder().build())
+ .build();
+ Futures.addCallback(sendDownlinkMsgsPack(Collections.singletonList(syncCompleteDownlinkMsg)), new FutureCallback() {
+ @Override
+ public void onSuccess(Void result) {
+ syncCompleted = true;
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ log.error("[{}][{}] Exception during sending sync complete", edge.getTenantId(), edge.getId(), t);
+ }
+ }, ctx.getGrpcCallbackExecutorService());
+ }
+ }
+
private void onUplinkMsg(UplinkMsg uplinkMsg) {
ListenableFuture> future = processUplinkMsg(uplinkMsg);
- Futures.addCallback(future, new FutureCallback>() {
+ Futures.addCallback(future, new FutureCallback<>() {
@Override
public void onSuccess(@Nullable List result) {
- UplinkResponseMsg uplinkResponseMsg = UplinkResponseMsg.newBuilder().setSuccess(true).build();
+ UplinkResponseMsg uplinkResponseMsg = UplinkResponseMsg.newBuilder()
+ .setUplinkMsgId(uplinkMsg.getUplinkMsgId())
+ .setSuccess(true).build();
sendDownlinkMsg(ResponseMsg.newBuilder()
.setUplinkResponseMsg(uplinkResponseMsg)
.build());
@@ -220,22 +239,32 @@ public final class EdgeGrpcSession implements Closeable {
@Override
public void onFailure(Throwable t) {
- UplinkResponseMsg uplinkResponseMsg = UplinkResponseMsg.newBuilder().setSuccess(false).setErrorMsg(t.getMessage()).build();
+ String errorMsg = t.getMessage() != null ? t.getMessage() : "";
+ UplinkResponseMsg uplinkResponseMsg = UplinkResponseMsg.newBuilder()
+ .setUplinkMsgId(uplinkMsg.getUplinkMsgId())
+ .setSuccess(false).setErrorMsg(errorMsg).build();
sendDownlinkMsg(ResponseMsg.newBuilder()
.setUplinkResponseMsg(uplinkResponseMsg)
.build());
}
- }, MoreExecutors.directExecutor());
+ }, ctx.getGrpcCallbackExecutorService());
}
private void onDownlinkResponse(DownlinkResponseMsg msg) {
try {
if (msg.getSuccess()) {
+ sessionState.getPendingMsgsMap().remove(msg.getDownlinkMsgId());
log.debug("[{}] Msg has been processed successfully! {}", edge.getRoutingKey(), msg);
} else {
log.error("[{}] Msg processing failed! Error msg: {}", edge.getRoutingKey(), msg.getErrorMsg());
}
- latch.countDown();
+ if (sessionState.getPendingMsgsMap().isEmpty()) {
+ log.debug("[{}] Pending msgs map is empty. Stopping current iteration {}", edge.getRoutingKey(), msg);
+ if (sessionState.getScheduledSendDownlinkTask() != null) {
+ sessionState.getScheduledSendDownlinkTask().cancel(false);
+ }
+ sessionState.getSendDownlinkMsgsFuture().set(null);
+ }
} catch (Exception e) {
log.error("[{}] Can't process downlink response message [{}]", this.sessionId, msg, e);
}
@@ -244,8 +273,8 @@ public final class EdgeGrpcSession implements Closeable {
private void sendDownlinkMsg(ResponseMsg downlinkMsg) {
log.trace("[{}] Sending downlink msg [{}]", this.sessionId, downlinkMsg);
if (isConnected()) {
+ downlinkMsgLock.lock();
try {
- downlinkMsgLock.lock();
outputStream.onNext(downlinkMsg);
} catch (Exception e) {
log.error("[{}] Failed to send downlink message [{}]", this.sessionId, downlinkMsg, e);
@@ -269,149 +298,192 @@ public final class EdgeGrpcSession implements Closeable {
sendDownlinkMsg(edgeConfigMsg);
}
- void processEdgeEvents() throws ExecutionException, InterruptedException {
- log.trace("[{}] processHandleMessages started", this.sessionId);
- if (isConnected()) {
+ ListenableFuture processEdgeEvents() throws Exception {
+ SettableFuture result = SettableFuture.create();
+ log.trace("[{}] starting processing edge events", this.sessionId);
+ if (isConnected() && isSyncCompleted()) {
Long queueStartTs = getQueueStartTs().get();
- TimePageLink pageLink = new TimePageLink(
- ctx.getEdgeEventStorageSettings().getMaxReadRecordsCount(),
- 0,
- null,
- new SortOrder("createdTime", SortOrder.Direction.ASC),
+ GeneralEdgeEventFetcher fetcher = new GeneralEdgeEventFetcher(
queueStartTs,
- null);
- PageData pageData;
- UUID ifOffset = null;
- boolean success = true;
- do {
- pageData = ctx.getEdgeEventService().findEdgeEvents(edge.getTenantId(), edge.getId(), pageLink, true);
- if (isConnected() && !pageData.getData().isEmpty()) {
- log.trace("[{}] [{}] event(s) are going to be processed.", this.sessionId, pageData.getData().size());
- List downlinkMsgsPack = convertToDownlinkMsgsPack(pageData.getData());
- log.trace("[{}] [{}] downlink msg(s) are going to be send.", this.sessionId, downlinkMsgsPack.size());
-
- latch = new CountDownLatch(downlinkMsgsPack.size());
- for (DownlinkMsg downlinkMsg : downlinkMsgsPack) {
- sendDownlinkMsg(ResponseMsg.newBuilder()
- .setDownlinkMsg(downlinkMsg)
- .build());
+ ctx.getEdgeEventService());
+ ListenableFuture ifOffsetFuture = startProcessingEdgeEvents(fetcher);
+ Futures.addCallback(ifOffsetFuture, new FutureCallback<>() {
+ @Override
+ public void onSuccess(@Nullable UUID ifOffset) {
+ if (ifOffset != null) {
+ Long newStartTs = Uuids.unixTimestamp(ifOffset);
+ ListenableFuture> updateFuture = updateQueueStartTs(newStartTs);
+ Futures.addCallback(updateFuture, new FutureCallback<>() {
+ @Override
+ public void onSuccess(@Nullable List list) {
+ log.debug("[{}] queue offset was updated [{}][{}]", sessionId, ifOffset, newStartTs);
+ result.set(null);
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ log.error("[{}] Failed to update queue offset [{}]", sessionId, ifOffset, t);
+ result.setException(t);
+ }
+ }, ctx.getGrpcCallbackExecutorService());
+ } else {
+ log.trace("[{}] ifOffset is null. Skipping iteration without db update", sessionId);
+ result.set(null);
}
+ }
- ifOffset = pageData.getData().get(pageData.getData().size() - 1).getUuidId();
-
- success = latch.await(10, TimeUnit.SECONDS);
- if (!success) {
- log.warn("[{}] Failed to deliver the batch: {}", this.sessionId, downlinkMsgsPack);
- }
+ @Override
+ public void onFailure(Throwable t) {
+ log.error("[{}] Failed to process events", sessionId, t);
+ result.setException(t);
}
- if (isConnected() && (!success || pageData.hasNext())) {
- try {
- Thread.sleep(ctx.getEdgeEventStorageSettings().getSleepIntervalBetweenBatches());
- } catch (InterruptedException e) {
- log.error("[{}] Error during sleep between batches", this.sessionId, e);
- }
- if (success) {
- pageLink = pageLink.nextPageLink();
+ }, ctx.getGrpcCallbackExecutorService());
+ } else {
+ log.trace("[{}] edge is not connected or sync is not completed. Skipping iteration", sessionId);
+ result.set(null);
+ }
+ return result;
+ }
+
+ private ListenableFuture startProcessingEdgeEvents(EdgeEventFetcher fetcher) {
+ SettableFuture result = SettableFuture.create();
+ PageLink pageLink = fetcher.getPageLink(ctx.getEdgeEventStorageSettings().getMaxReadRecordsCount());
+ processEdgeEvents(fetcher, pageLink, result);
+ return result;
+ }
+
+ private void processEdgeEvents(EdgeEventFetcher fetcher, PageLink pageLink, SettableFuture result) {
+ try {
+ PageData pageData = fetcher.fetchEdgeEvents(edge.getTenantId(), edge, pageLink);
+ if (isConnected() && !pageData.getData().isEmpty()) {
+ log.trace("[{}] [{}] event(s) are going to be processed.", this.sessionId, pageData.getData().size());
+ List downlinkMsgsPack = convertToDownlinkMsgsPack(pageData.getData());
+ Futures.addCallback(sendDownlinkMsgsPack(downlinkMsgsPack), new FutureCallback() {
+ @Override
+ public void onSuccess(@Nullable Void tmp) {
+ if (isConnected() && pageData.hasNext()) {
+ processEdgeEvents(fetcher, pageLink.nextPageLink(), result);
+ } else {
+ UUID ifOffset = pageData.getData().get(pageData.getData().size() - 1).getUuidId();
+ result.set(ifOffset);
+ }
}
- }
- } while (isConnected() && (!success || pageData.hasNext()));
- if (ifOffset != null) {
- Long newStartTs = Uuids.unixTimestamp(ifOffset);
- updateQueueStartTs(newStartTs);
+ @Override
+ public void onFailure(Throwable t) {
+ log.error("[{}] Failed to send downlink msgs pack", sessionId, t);
+ result.setException(t);
+ }
+ }, ctx.getGrpcCallbackExecutorService());
+ } else {
+ log.trace("[{}] no event(s) found. Stop processing edge events", this.sessionId);
+ result.set(null);
}
+ } catch (Exception e) {
+ log.error("[{}] Failed to fetch edge events", this.sessionId, e);
+ result.setException(e);
}
- log.trace("[{}] processHandleMessages finished", this.sessionId);
}
- private List convertToDownlinkMsgsPack(List edgeEvents) {
- List result = new ArrayList<>();
- for (EdgeEvent edgeEvent : edgeEvents) {
- log.trace("[{}] Processing edge event [{}]", this.sessionId, edgeEvent);
+ private ListenableFuture sendDownlinkMsgsPack(List downlinkMsgsPack) {
+ if (sessionState.getSendDownlinkMsgsFuture() != null && !sessionState.getSendDownlinkMsgsFuture().isDone()) {
+ String erroMsg = "[" + this.sessionId + "] Previous send downdlink future was not properly completed, stopping it now";
+ log.error(erroMsg);
+ sessionState.getSendDownlinkMsgsFuture().setException(new RuntimeException(erroMsg));
+ }
+ sessionState.setSendDownlinkMsgsFuture(SettableFuture.create());
+ sessionState.getPendingMsgsMap().clear();
+ downlinkMsgsPack.forEach(msg -> sessionState.getPendingMsgsMap().put(msg.getDownlinkMsgId(), msg));
+ scheduleDownlinkMsgsPackSend(true);
+ return sessionState.getSendDownlinkMsgsFuture();
+ }
+
+ private void scheduleDownlinkMsgsPackSend(boolean firstRun) {
+ Runnable sendDownlinkMsgsTask = () -> {
try {
- DownlinkMsg downlinkMsg = null;
- switch (edgeEvent.getAction()) {
- case UPDATED:
- case ADDED:
- case DELETED:
- case ASSIGNED_TO_EDGE:
- case UNASSIGNED_FROM_EDGE:
- case ALARM_ACK:
- case ALARM_CLEAR:
- case CREDENTIALS_UPDATED:
- case RELATION_ADD_OR_UPDATE:
- case RELATION_DELETED:
- case ASSIGNED_TO_CUSTOMER:
- case UNASSIGNED_FROM_CUSTOMER:
- downlinkMsg = processEntityMessage(edgeEvent, edgeEvent.getAction());
- break;
- case ATTRIBUTES_UPDATED:
- case POST_ATTRIBUTES:
- case ATTRIBUTES_DELETED:
- case TIMESERIES_UPDATED:
- downlinkMsg = processTelemetryMessage(edgeEvent);
- break;
- case CREDENTIALS_REQUEST:
- downlinkMsg = processCredentialsRequestMessage(edgeEvent);
- break;
- case ENTITY_MERGE_REQUEST:
- downlinkMsg = processEntityMergeRequestMessage(edgeEvent);
- break;
- case RPC_CALL:
- downlinkMsg = processRpcCallMsg(edgeEvent);
- break;
- }
- if (downlinkMsg != null) {
- result.add(downlinkMsg);
+ if (isConnected() && sessionState.getPendingMsgsMap().values().size() > 0) {
+ if (!firstRun) {
+ log.warn("[{}] Failed to deliver the batch: {}", this.sessionId, sessionState.getPendingMsgsMap().values());
+ }
+ log.trace("[{}] [{}] downlink msg(s) are going to be send.", this.sessionId, sessionState.getPendingMsgsMap().values().size());
+ List copy = new ArrayList<>(sessionState.getPendingMsgsMap().values());
+ for (DownlinkMsg downlinkMsg : copy) {
+ sendDownlinkMsg(ResponseMsg.newBuilder()
+ .setDownlinkMsg(downlinkMsg)
+ .build());
+ }
+ scheduleDownlinkMsgsPackSend(false);
+ } else {
+ sessionState.getSendDownlinkMsgsFuture().set(null);
}
} catch (Exception e) {
- log.error("Exception during processing records from queue", e);
+ sessionState.getSendDownlinkMsgsFuture().setException(e);
}
+ };
+
+ if (firstRun) {
+ sendDownlinkExecutorService.submit(sendDownlinkMsgsTask);
+ } else {
+ sessionState.setScheduledSendDownlinkTask(
+ sendDownlinkExecutorService.schedule(
+ sendDownlinkMsgsTask,
+ ctx.getEdgeEventStorageSettings().getSleepIntervalBetweenBatches(),
+ TimeUnit.MILLISECONDS)
+ );
}
- return result;
+
}
- private DownlinkMsg processEntityMergeRequestMessage(EdgeEvent edgeEvent) {
+ private DownlinkMsg convertToDownlinkMsg(EdgeEvent edgeEvent) {
+ log.trace("[{}][{}] converting edge event to downlink msg [{}]", edge.getTenantId(), this.sessionId, edgeEvent);
DownlinkMsg downlinkMsg = null;
- if (EdgeEventType.DEVICE.equals(edgeEvent.getType())) {
- DeviceId deviceId = new DeviceId(edgeEvent.getEntityId());
- Device device = ctx.getDeviceService().findDeviceById(edge.getTenantId(), deviceId);
- CustomerId customerId = getCustomerIdIfEdgeAssignedToCustomer(device);
- String conflictName = null;
- if(edgeEvent.getBody() != null) {
- conflictName = edgeEvent.getBody().get("conflictName").asText();
+ try {
+ switch (edgeEvent.getAction()) {
+ case UPDATED:
+ case ADDED:
+ case DELETED:
+ case ASSIGNED_TO_EDGE:
+ case UNASSIGNED_FROM_EDGE:
+ case ALARM_ACK:
+ case ALARM_CLEAR:
+ case CREDENTIALS_UPDATED:
+ case RELATION_ADD_OR_UPDATE:
+ case RELATION_DELETED:
+ case ASSIGNED_TO_CUSTOMER:
+ case UNASSIGNED_FROM_CUSTOMER:
+ downlinkMsg = processEntityMessage(edgeEvent, edgeEvent.getAction());
+ log.trace("[{}][{}] entity message processed [{}]", edgeEvent.getTenantId(), this.sessionId, downlinkMsg);
+ break;
+ case ATTRIBUTES_UPDATED:
+ case POST_ATTRIBUTES:
+ case ATTRIBUTES_DELETED:
+ case TIMESERIES_UPDATED:
+ downlinkMsg = ctx.getTelemetryProcessor().processTelemetryMessageToEdge(edgeEvent);
+ break;
+ case CREDENTIALS_REQUEST:
+ downlinkMsg = ctx.getEntityProcessor().processCredentialsRequestMessageToEdge(edgeEvent);
+ break;
+ case ENTITY_MERGE_REQUEST:
+ downlinkMsg = ctx.getEntityProcessor().processEntityMergeRequestMessageToEdge(edge, edgeEvent);
+ break;
+ case RPC_CALL:
+ downlinkMsg = ctx.getDeviceProcessor().processRpcCallMsgToEdge(edgeEvent);
+ break;
+ default:
+ log.warn("[{}][{}] Unsupported action type [{}]", edge.getTenantId(), this.sessionId, edgeEvent.getAction());
}
- DeviceUpdateMsg d = ctx.getDeviceMsgConstructor()
- .constructDeviceUpdatedMsg(UpdateMsgType.ENTITY_MERGE_RPC_MESSAGE, device, customerId, conflictName);
- downlinkMsg = DownlinkMsg.newBuilder()
- .addAllDeviceUpdateMsg(Collections.singletonList(d))
- .build();
+ } catch (Exception e) {
+ log.error("[{}][{}] Exception during converting edge event to downlink msg", edge.getTenantId(), this.sessionId, e);
}
return downlinkMsg;
}
- private DownlinkMsg processRpcCallMsg(EdgeEvent edgeEvent) {
- log.trace("Executing processRpcCall, edgeEvent [{}]", edgeEvent);
- DeviceRpcCallMsg deviceRpcCallMsg =
- ctx.getDeviceMsgConstructor().constructDeviceRpcCallMsg(edgeEvent.getEntityId(), edgeEvent.getBody());
- return DownlinkMsg.newBuilder()
- .addAllDeviceRpcCallMsg(Collections.singletonList(deviceRpcCallMsg))
- .build();
- }
-
- private DownlinkMsg processCredentialsRequestMessage(EdgeEvent edgeEvent) {
- DownlinkMsg downlinkMsg = null;
- if (EdgeEventType.DEVICE.equals(edgeEvent.getType())) {
- DeviceId deviceId = new DeviceId(edgeEvent.getEntityId());
- DeviceCredentialsRequestMsg deviceCredentialsRequestMsg = DeviceCredentialsRequestMsg.newBuilder()
- .setDeviceIdMSB(deviceId.getId().getMostSignificantBits())
- .setDeviceIdLSB(deviceId.getId().getLeastSignificantBits())
- .build();
- DownlinkMsg.Builder builder = DownlinkMsg.newBuilder()
- .addAllDeviceCredentialsRequestMsg(Collections.singletonList(deviceCredentialsRequestMsg));
- downlinkMsg = builder.build();
- }
- return downlinkMsg;
+ private List convertToDownlinkMsgsPack(List edgeEvents) {
+ return edgeEvents
+ .stream()
+ .map(this::convertToDownlinkMsg)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList());
}
private ListenableFuture getQueueStartTs() {
@@ -424,52 +496,14 @@ public final class EdgeGrpcSession implements Closeable {
} else {
return 0L;
}
- }, ctx.getDbCallbackExecutor());
+ }, ctx.getGrpcCallbackExecutorService());
}
- private void updateQueueStartTs(Long newStartTs) {
+ private ListenableFuture> updateQueueStartTs(Long newStartTs) {
log.trace("[{}] updating QueueStartTs [{}][{}]", this.sessionId, edge.getId(), newStartTs);
newStartTs = ++newStartTs; // increments ts by 1 - next edge event search starts from current offset + 1
List attributes = Collections.singletonList(new BaseAttributeKvEntry(new LongDataEntry(QUEUE_START_TS_ATTR_KEY, newStartTs), System.currentTimeMillis()));
- ctx.getAttributesService().save(edge.getTenantId(), edge.getId(), DataConstants.SERVER_SCOPE, attributes);
- }
-
- private DownlinkMsg processTelemetryMessage(EdgeEvent edgeEvent) {
- log.trace("[{}] Executing processTelemetryMessage, edgeEvent [{}]", this.sessionId, edgeEvent);
- EntityId entityId = null;
- switch (edgeEvent.getType()) {
- case DEVICE:
- entityId = new DeviceId(edgeEvent.getEntityId());
- break;
- case ASSET:
- entityId = new AssetId(edgeEvent.getEntityId());
- break;
- case ENTITY_VIEW:
- entityId = new EntityViewId(edgeEvent.getEntityId());
- break;
- case DASHBOARD:
- entityId = new DashboardId(edgeEvent.getEntityId());
- break;
- case TENANT:
- entityId = new TenantId(edgeEvent.getEntityId());
- break;
- case CUSTOMER:
- entityId = new CustomerId(edgeEvent.getEntityId());
- break;
- case EDGE:
- entityId = new EdgeId(edgeEvent.getEntityId());
- break;
- }
- DownlinkMsg downlinkMsg = null;
- if (entityId != null) {
- log.debug("[{}] Sending telemetry data msg, entityId [{}], body [{}]", this.sessionId, edgeEvent.getEntityId(), edgeEvent.getBody());
- try {
- downlinkMsg = constructEntityDataProtoMsg(entityId, edgeEvent.getAction(), JsonUtils.parse(mapper.writeValueAsString(edgeEvent.getBody())));
- } catch (Exception e) {
- log.warn("[{}] Can't send telemetry data msg, entityId [{}], body [{}]", this.sessionId, edgeEvent.getEntityId(), edgeEvent.getBody(), e);
- }
- }
- return downlinkMsg;
+ return ctx.getAttributesService().save(edge.getTenantId(), edge.getId(), DataConstants.SERVER_SCOPE, attributes);
}
private DownlinkMsg processEntityMessage(EdgeEvent edgeEvent, EdgeEventActionType action) {
@@ -477,411 +511,39 @@ public final class EdgeGrpcSession implements Closeable {
log.trace("Executing processEntityMessage, edgeEvent [{}], action [{}], msgType [{}]", edgeEvent, action, msgType);
switch (edgeEvent.getType()) {
case DEVICE:
- return processDevice(edgeEvent, msgType, action);
+ return ctx.getDeviceProcessor().processDeviceToEdge(edge, edgeEvent, msgType, action);
case DEVICE_PROFILE:
- return processDeviceProfile(edgeEvent, msgType, action);
+ return ctx.getDeviceProfileProcessor().processDeviceProfileToEdge(edgeEvent, msgType, action);
case ASSET:
- return processAsset(edgeEvent, msgType, action);
+ return ctx.getAssetProcessor().processAssetToEdge(edge, edgeEvent, msgType, action);
case ENTITY_VIEW:
- return processEntityView(edgeEvent, msgType, action);
+ return ctx.getEntityViewProcessor().processEntityViewToEdge(edge, edgeEvent, msgType, action);
case DASHBOARD:
- return processDashboard(edgeEvent, msgType, action);
+ return ctx.getDashboardProcessor().processDashboardToEdge(edge, edgeEvent, msgType, action);
case CUSTOMER:
- return processCustomer(edgeEvent, msgType, action);
+ return ctx.getCustomerProcessor().processCustomerToEdge(edgeEvent, msgType, action);
case RULE_CHAIN:
- return processRuleChain(edgeEvent, msgType, action);
+ return ctx.getRuleChainProcessor().processRuleChainToEdge(edge, edgeEvent, msgType, action);
case RULE_CHAIN_METADATA:
- return processRuleChainMetadata(edgeEvent, msgType);
+ return ctx.getRuleChainProcessor().processRuleChainMetadataToEdge(edgeEvent, msgType);
case ALARM:
- return processAlarm(edgeEvent, msgType);
+ return ctx.getAlarmProcessor().processAlarmToEdge(edge, edgeEvent, msgType);
case USER:
- return processUser(edgeEvent, msgType, action);
+ return ctx.getUserProcessor().processUserToEdge(edge, edgeEvent, msgType, action);
case RELATION:
- return processRelation(edgeEvent, msgType);
+ return ctx.getRelationProcessor().processRelationToEdge(edgeEvent, msgType);
case WIDGETS_BUNDLE:
- return processWidgetsBundle(edgeEvent, msgType, action);
+ return ctx.getWidgetBundleProcessor().processWidgetsBundleToEdge(edgeEvent, msgType, action);
case WIDGET_TYPE:
- return processWidgetType(edgeEvent, msgType, action);
+ return ctx.getWidgetTypeProcessor().processWidgetTypeToEdge(edgeEvent, msgType, action);
case ADMIN_SETTINGS:
- return processAdminSettings(edgeEvent);
+ return ctx.getAdminSettingsProcessor().processAdminSettingsToEdge(edgeEvent);
default:
log.warn("Unsupported edge event type [{}]", edgeEvent);
return null;
}
}
- private DownlinkMsg processDevice(EdgeEvent edgeEvent, UpdateMsgType msgType, EdgeEventActionType edgeEdgeEventActionType) {
- DeviceId deviceId = new DeviceId(edgeEvent.getEntityId());
- DownlinkMsg downlinkMsg = null;
- switch (edgeEdgeEventActionType) {
- case ADDED:
- case UPDATED:
- case ASSIGNED_TO_EDGE:
- case ASSIGNED_TO_CUSTOMER:
- case UNASSIGNED_FROM_CUSTOMER:
- Device device = ctx.getDeviceService().findDeviceById(edgeEvent.getTenantId(), deviceId);
- if (device != null) {
- CustomerId customerId = getCustomerIdIfEdgeAssignedToCustomer(device);
- DeviceUpdateMsg deviceUpdateMsg =
- ctx.getDeviceMsgConstructor().constructDeviceUpdatedMsg(msgType, device, customerId, null);
- downlinkMsg = DownlinkMsg.newBuilder()
- .addAllDeviceUpdateMsg(Collections.singletonList(deviceUpdateMsg))
- .build();
- }
- break;
- case DELETED:
- case UNASSIGNED_FROM_EDGE:
- DeviceUpdateMsg deviceUpdateMsg =
- ctx.getDeviceMsgConstructor().constructDeviceDeleteMsg(deviceId);
- downlinkMsg = DownlinkMsg.newBuilder()
- .addAllDeviceUpdateMsg(Collections.singletonList(deviceUpdateMsg))
- .build();
- break;
- case CREDENTIALS_UPDATED:
- DeviceCredentials deviceCredentials = ctx.getDeviceCredentialsService().findDeviceCredentialsByDeviceId(edge.getTenantId(), deviceId);
- if (deviceCredentials != null) {
- DeviceCredentialsUpdateMsg deviceCredentialsUpdateMsg =
- ctx.getDeviceMsgConstructor().constructDeviceCredentialsUpdatedMsg(deviceCredentials);
- downlinkMsg = DownlinkMsg.newBuilder()
- .addAllDeviceCredentialsUpdateMsg(Collections.singletonList(deviceCredentialsUpdateMsg))
- .build();
- }
- break;
- }
- log.trace("[{}] device processed [{}]", this.sessionId, downlinkMsg);
- return downlinkMsg;
- }
-
- private DownlinkMsg processDeviceProfile(EdgeEvent edgeEvent, UpdateMsgType msgType, EdgeEventActionType action) {
- DeviceProfileId deviceProfileId = new DeviceProfileId(edgeEvent.getEntityId());
- DownlinkMsg downlinkMsg = null;
- switch (action) {
- case ADDED:
- case UPDATED:
- DeviceProfile deviceProfile = ctx.getDeviceProfileService().findDeviceProfileById(edgeEvent.getTenantId(), deviceProfileId);
- if (deviceProfile != null) {
- DeviceProfileUpdateMsg deviceProfileUpdateMsg =
- ctx.getDeviceProfileMsgConstructor().constructDeviceProfileUpdatedMsg(msgType, deviceProfile);
- downlinkMsg = DownlinkMsg.newBuilder()
- .addAllDeviceProfileUpdateMsg(Collections.singletonList(deviceProfileUpdateMsg))
- .build();
- }
- break;
- case DELETED:
- DeviceProfileUpdateMsg deviceProfileUpdateMsg =
- ctx.getDeviceProfileMsgConstructor().constructDeviceProfileDeleteMsg(deviceProfileId);
- downlinkMsg = DownlinkMsg.newBuilder()
- .addAllDeviceProfileUpdateMsg(Collections.singletonList(deviceProfileUpdateMsg))
- .build();
- break;
- }
- log.trace("[{}] device profile processed [{}]", this.sessionId, downlinkMsg);
- return downlinkMsg;
- }
-
- private DownlinkMsg processAsset(EdgeEvent edgeEvent, UpdateMsgType msgType, EdgeEventActionType action) {
- AssetId assetId = new AssetId(edgeEvent.getEntityId());
- DownlinkMsg downlinkMsg = null;
- switch (action) {
- case ADDED:
- case UPDATED:
- case ASSIGNED_TO_EDGE:
- case ASSIGNED_TO_CUSTOMER:
- case UNASSIGNED_FROM_CUSTOMER:
- Asset asset = ctx.getAssetService().findAssetById(edgeEvent.getTenantId(), assetId);
- if (asset != null) {
- CustomerId customerId = getCustomerIdIfEdgeAssignedToCustomer(asset);
- AssetUpdateMsg assetUpdateMsg =
- ctx.getAssetMsgConstructor().constructAssetUpdatedMsg(msgType, asset, customerId);
- downlinkMsg = DownlinkMsg.newBuilder()
- .addAllAssetUpdateMsg(Collections.singletonList(assetUpdateMsg))
- .build();
- }
- break;
- case DELETED:
- case UNASSIGNED_FROM_EDGE:
- AssetUpdateMsg assetUpdateMsg =
- ctx.getAssetMsgConstructor().constructAssetDeleteMsg(assetId);
- downlinkMsg = DownlinkMsg.newBuilder()
- .addAllAssetUpdateMsg(Collections.singletonList(assetUpdateMsg))
- .build();
- break;
- }
- log.trace("[{}] asset processed [{}]", this.sessionId, downlinkMsg);
- return downlinkMsg;
- }
-
- private DownlinkMsg processEntityView(EdgeEvent edgeEvent, UpdateMsgType msgType, EdgeEventActionType action) {
- EntityViewId entityViewId = new EntityViewId(edgeEvent.getEntityId());
- DownlinkMsg downlinkMsg = null;
- switch (action) {
- case ADDED:
- case UPDATED:
- case ASSIGNED_TO_EDGE:
- case ASSIGNED_TO_CUSTOMER:
- case UNASSIGNED_FROM_CUSTOMER:
- EntityView entityView = ctx.getEntityViewService().findEntityViewById(edgeEvent.getTenantId(), entityViewId);
- if (entityView != null) {
- CustomerId customerId = getCustomerIdIfEdgeAssignedToCustomer(entityView);
- EntityViewUpdateMsg entityViewUpdateMsg =
- ctx.getEntityViewMsgConstructor().constructEntityViewUpdatedMsg(msgType, entityView, customerId);
- downlinkMsg = DownlinkMsg.newBuilder()
- .addAllEntityViewUpdateMsg(Collections.singletonList(entityViewUpdateMsg))
- .build();
- }
- break;
- case DELETED:
- case UNASSIGNED_FROM_EDGE:
- EntityViewUpdateMsg entityViewUpdateMsg =
- ctx.getEntityViewMsgConstructor().constructEntityViewDeleteMsg(entityViewId);
- downlinkMsg = DownlinkMsg.newBuilder()
- .addAllEntityViewUpdateMsg(Collections.singletonList(entityViewUpdateMsg))
- .build();
- break;
- }
- log.trace("[{}] entity view processed [{}]", this.sessionId, downlinkMsg);
- return downlinkMsg;
- }
-
- private DownlinkMsg processDashboard(EdgeEvent edgeEvent, UpdateMsgType msgType, EdgeEventActionType action) {
- DashboardId dashboardId = new DashboardId(edgeEvent.getEntityId());
- DownlinkMsg downlinkMsg = null;
- switch (action) {
- case ADDED:
- case UPDATED:
- case ASSIGNED_TO_EDGE:
- case ASSIGNED_TO_CUSTOMER:
- case UNASSIGNED_FROM_CUSTOMER:
- Dashboard dashboard = ctx.getDashboardService().findDashboardById(edgeEvent.getTenantId(), dashboardId);
- if (dashboard != null) {
- CustomerId customerId = null;
- if (!edge.getCustomerId().isNullUid() && dashboard.isAssignedToCustomer(edge.getCustomerId())) {
- customerId = edge.getCustomerId();
- }
- DashboardUpdateMsg dashboardUpdateMsg =
- ctx.getDashboardMsgConstructor().constructDashboardUpdatedMsg(msgType, dashboard, customerId);
- downlinkMsg = DownlinkMsg.newBuilder()
- .addAllDashboardUpdateMsg(Collections.singletonList(dashboardUpdateMsg))
- .build();
- }
- break;
- case DELETED:
- case UNASSIGNED_FROM_EDGE:
- DashboardUpdateMsg dashboardUpdateMsg =
- ctx.getDashboardMsgConstructor().constructDashboardDeleteMsg(dashboardId);
- downlinkMsg = DownlinkMsg.newBuilder()
- .addAllDashboardUpdateMsg(Collections.singletonList(dashboardUpdateMsg))
- .build();
- break;
- }
- log.trace("[{}] dashboard processed [{}]", this.sessionId, downlinkMsg);
- return downlinkMsg;
- }
-
- private DownlinkMsg processCustomer(EdgeEvent edgeEvent, UpdateMsgType msgType, EdgeEventActionType action) {
- CustomerId customerId = new CustomerId(edgeEvent.getEntityId());
- DownlinkMsg downlinkMsg = null;
- switch (action) {
- case ADDED:
- case UPDATED:
- Customer customer = ctx.getCustomerService().findCustomerById(edgeEvent.getTenantId(), customerId);
- if (customer != null) {
- CustomerUpdateMsg customerUpdateMsg =
- ctx.getCustomerMsgConstructor().constructCustomerUpdatedMsg(msgType, customer);
- downlinkMsg = DownlinkMsg.newBuilder()
- .addAllCustomerUpdateMsg(Collections.singletonList(customerUpdateMsg))
- .build();
- }
- break;
- case DELETED:
- CustomerUpdateMsg customerUpdateMsg =
- ctx.getCustomerMsgConstructor().constructCustomerDeleteMsg(customerId);
- downlinkMsg = DownlinkMsg.newBuilder()
- .addAllCustomerUpdateMsg(Collections.singletonList(customerUpdateMsg))
- .build();
- break;
- }
- log.trace("[{}] customer processed [{}]", this.sessionId, downlinkMsg);
- return downlinkMsg;
- }
-
- private DownlinkMsg processRuleChain(EdgeEvent edgeEvent, UpdateMsgType msgType, EdgeEventActionType action) {
- RuleChainId ruleChainId = new RuleChainId(edgeEvent.getEntityId());
- DownlinkMsg downlinkMsg = null;
- switch (action) {
- case ADDED:
- case UPDATED:
- case ASSIGNED_TO_EDGE:
- RuleChain ruleChain = ctx.getRuleChainService().findRuleChainById(edgeEvent.getTenantId(), ruleChainId);
- if (ruleChain != null) {
- RuleChainUpdateMsg ruleChainUpdateMsg =
- ctx.getRuleChainMsgConstructor().constructRuleChainUpdatedMsg(edge.getRootRuleChainId(), msgType, ruleChain);
- downlinkMsg = DownlinkMsg.newBuilder()
- .addAllRuleChainUpdateMsg(Collections.singletonList(ruleChainUpdateMsg))
- .build();
- }
- break;
- case DELETED:
- case UNASSIGNED_FROM_EDGE:
- downlinkMsg = DownlinkMsg.newBuilder()
- .addAllRuleChainUpdateMsg(Collections.singletonList(ctx.getRuleChainMsgConstructor().constructRuleChainDeleteMsg(ruleChainId)))
- .build();
- break;
- }
- log.trace("[{}] rule chain processed [{}]", this.sessionId, downlinkMsg);
- return downlinkMsg;
- }
-
- private DownlinkMsg processRuleChainMetadata(EdgeEvent edgeEvent, UpdateMsgType msgType) {
- RuleChainId ruleChainId = new RuleChainId(edgeEvent.getEntityId());
- RuleChain ruleChain = ctx.getRuleChainService().findRuleChainById(edgeEvent.getTenantId(), ruleChainId);
- DownlinkMsg downlinkMsg = null;
- if (ruleChain != null) {
- RuleChainMetaData ruleChainMetaData = ctx.getRuleChainService().loadRuleChainMetaData(edgeEvent.getTenantId(), ruleChainId);
- RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg =
- ctx.getRuleChainMsgConstructor().constructRuleChainMetadataUpdatedMsg(msgType, ruleChainMetaData);
- if (ruleChainMetadataUpdateMsg != null) {
- downlinkMsg = DownlinkMsg.newBuilder()
- .addAllRuleChainMetadataUpdateMsg(Collections.singletonList(ruleChainMetadataUpdateMsg))
- .build();
- }
- }
- log.trace("[{}] rule chain metadata processed [{}]", this.sessionId, downlinkMsg);
- return downlinkMsg;
- }
-
- private DownlinkMsg processUser(EdgeEvent edgeEvent, UpdateMsgType msgType, EdgeEventActionType edgeEdgeEventActionType) {
- UserId userId = new UserId(edgeEvent.getEntityId());
- DownlinkMsg downlinkMsg = null;
- switch (edgeEdgeEventActionType) {
- case ADDED:
- case UPDATED:
- User user = ctx.getUserService().findUserById(edgeEvent.getTenantId(), userId);
- if (user != null) {
- CustomerId customerId = getCustomerIdIfEdgeAssignedToCustomer(user);
- downlinkMsg = DownlinkMsg.newBuilder()
- .addAllUserUpdateMsg(Collections.singletonList(ctx.getUserMsgConstructor().constructUserUpdatedMsg(msgType, user, customerId)))
- .build();
- }
- break;
- case DELETED:
- downlinkMsg = DownlinkMsg.newBuilder()
- .addAllUserUpdateMsg(Collections.singletonList(ctx.getUserMsgConstructor().constructUserDeleteMsg(userId)))
- .build();
- break;
- case CREDENTIALS_UPDATED:
- UserCredentials userCredentialsByUserId = ctx.getUserService().findUserCredentialsByUserId(edge.getTenantId(), userId);
- if (userCredentialsByUserId != null && userCredentialsByUserId.isEnabled()) {
- UserCredentialsUpdateMsg userCredentialsUpdateMsg =
- ctx.getUserMsgConstructor().constructUserCredentialsUpdatedMsg(userCredentialsByUserId);
- downlinkMsg = DownlinkMsg.newBuilder()
- .addAllUserCredentialsUpdateMsg(Collections.singletonList(userCredentialsUpdateMsg))
- .build();
- }
- }
- log.trace("[{}] user processed [{}]", this.sessionId, downlinkMsg);
- return downlinkMsg;
- }
-
- private CustomerId getCustomerIdIfEdgeAssignedToCustomer(HasCustomerId hasCustomerIdEntity) {
- if (!edge.getCustomerId().isNullUid() && edge.getCustomerId().equals(hasCustomerIdEntity.getCustomerId())) {
- return edge.getCustomerId();
- } else {
- return null;
- }
- }
-
- private DownlinkMsg processRelation(EdgeEvent edgeEvent, UpdateMsgType msgType) {
- EntityRelation entityRelation = mapper.convertValue(edgeEvent.getBody(), EntityRelation.class);
- RelationUpdateMsg r = ctx.getRelationMsgConstructor().constructRelationUpdatedMsg(msgType, entityRelation);
- DownlinkMsg downlinkMsg = DownlinkMsg.newBuilder()
- .addAllRelationUpdateMsg(Collections.singletonList(r))
- .build();
- log.trace("[{}] relation processed [{}]", this.sessionId, downlinkMsg);
- return downlinkMsg;
- }
-
- private DownlinkMsg processAlarm(EdgeEvent edgeEvent, UpdateMsgType msgType) {
- DownlinkMsg downlinkMsg = null;
- try {
- AlarmId alarmId = new AlarmId(edgeEvent.getEntityId());
- Alarm alarm = ctx.getAlarmService().findAlarmByIdAsync(edgeEvent.getTenantId(), alarmId).get();
- if (alarm != null) {
- downlinkMsg = DownlinkMsg.newBuilder()
- .addAllAlarmUpdateMsg(Collections.singletonList(ctx.getAlarmMsgConstructor().constructAlarmUpdatedMsg(edge.getTenantId(), msgType, alarm)))
- .build();
- }
- } catch (Exception e) {
- log.error("Can't process alarm msg [{}] [{}]", edgeEvent, msgType, e);
- }
- log.trace("[{}] alarm processed [{}]", this.sessionId, downlinkMsg);
- return downlinkMsg;
- }
-
- private DownlinkMsg processWidgetsBundle(EdgeEvent edgeEvent, UpdateMsgType msgType, EdgeEventActionType edgeEdgeEventActionType) {
- WidgetsBundleId widgetsBundleId = new WidgetsBundleId(edgeEvent.getEntityId());
- DownlinkMsg downlinkMsg = null;
- switch (edgeEdgeEventActionType) {
- case ADDED:
- case UPDATED:
- WidgetsBundle widgetsBundle = ctx.getWidgetsBundleService().findWidgetsBundleById(edgeEvent.getTenantId(), widgetsBundleId);
- if (widgetsBundle != null) {
- WidgetsBundleUpdateMsg widgetsBundleUpdateMsg =
- ctx.getWidgetsBundleMsgConstructor().constructWidgetsBundleUpdateMsg(msgType, widgetsBundle);
- downlinkMsg = DownlinkMsg.newBuilder()
- .addAllWidgetsBundleUpdateMsg(Collections.singletonList(widgetsBundleUpdateMsg))
- .build();
- }
- break;
- case DELETED:
- WidgetsBundleUpdateMsg widgetsBundleUpdateMsg =
- ctx.getWidgetsBundleMsgConstructor().constructWidgetsBundleDeleteMsg(widgetsBundleId);
- downlinkMsg = DownlinkMsg.newBuilder()
- .addAllWidgetsBundleUpdateMsg(Collections.singletonList(widgetsBundleUpdateMsg))
- .build();
- break;
- }
- log.trace("[{}] widget bundle processed [{}]", this.sessionId, downlinkMsg);
- return downlinkMsg;
- }
-
- private DownlinkMsg processWidgetType(EdgeEvent edgeEvent, UpdateMsgType msgType, EdgeEventActionType edgeEdgeEventActionType) {
- WidgetTypeId widgetTypeId = new WidgetTypeId(edgeEvent.getEntityId());
- DownlinkMsg downlinkMsg = null;
- switch (edgeEdgeEventActionType) {
- case ADDED:
- case UPDATED:
- WidgetType widgetType = ctx.getWidgetTypeService().findWidgetTypeById(edgeEvent.getTenantId(), widgetTypeId);
- if (widgetType != null) {
- WidgetTypeUpdateMsg widgetTypeUpdateMsg =
- ctx.getWidgetTypeMsgConstructor().constructWidgetTypeUpdateMsg(msgType, widgetType);
- downlinkMsg = DownlinkMsg.newBuilder()
- .addAllWidgetTypeUpdateMsg(Collections.singletonList(widgetTypeUpdateMsg))
- .build();
- }
- break;
- case DELETED:
- WidgetTypeUpdateMsg widgetTypeUpdateMsg =
- ctx.getWidgetTypeMsgConstructor().constructWidgetTypeDeleteMsg(widgetTypeId);
- downlinkMsg = DownlinkMsg.newBuilder()
- .addAllWidgetTypeUpdateMsg(Collections.singletonList(widgetTypeUpdateMsg))
- .build();
- break;
- }
- log.trace("[{}] widget type processed [{}]", this.sessionId, downlinkMsg);
- return downlinkMsg;
- }
-
- private DownlinkMsg processAdminSettings(EdgeEvent edgeEvent) {
- AdminSettings adminSettings = mapper.convertValue(edgeEvent.getBody(), AdminSettings.class);
- AdminSettingsUpdateMsg t = ctx.getAdminSettingsMsgConstructor().constructAdminSettingsUpdateMsg(adminSettings);
- DownlinkMsg downlinkMsg = DownlinkMsg.newBuilder()
- .addAllAdminSettingsUpdateMsg(Collections.singletonList(t))
- .build();
- log.trace("[{}] admin settings processed [{}]", this.sessionId, downlinkMsg);
- return downlinkMsg;
- }
-
private UpdateMsgType getResponseMsgType(EdgeEventActionType actionType) {
switch (actionType) {
case UPDATED:
@@ -906,71 +568,77 @@ public final class EdgeGrpcSession implements Closeable {
}
}
- private DownlinkMsg constructEntityDataProtoMsg(EntityId entityId, EdgeEventActionType actionType, JsonElement entityData) {
- EntityDataProto entityDataProto = ctx.getEntityDataMsgConstructor().constructEntityDataMsg(entityId, actionType, entityData);
- DownlinkMsg downlinkMsg = DownlinkMsg.newBuilder()
- .addAllEntityData(Collections.singletonList(entityDataProto))
- .build();
- log.trace("[{}] entity data proto processed [{}]", this.sessionId, downlinkMsg);
- return downlinkMsg;
- }
-
private ListenableFuture> processUplinkMsg(UplinkMsg uplinkMsg) {
List> result = new ArrayList<>();
try {
if (uplinkMsg.getEntityDataCount() > 0) {
for (EntityDataProto entityData : uplinkMsg.getEntityDataList()) {
- result.addAll(ctx.getTelemetryProcessor().onTelemetryUpdate(edge.getTenantId(), edge.getCustomerId(), entityData));
+ result.addAll(ctx.getTelemetryProcessor().processTelemetryFromEdge(edge.getTenantId(), edge.getCustomerId(), entityData));
}
}
if (uplinkMsg.getDeviceUpdateMsgCount() > 0) {
for (DeviceUpdateMsg deviceUpdateMsg : uplinkMsg.getDeviceUpdateMsgList()) {
- result.add(ctx.getDeviceProcessor().onDeviceUpdate(edge.getTenantId(), edge, deviceUpdateMsg));
+ result.add(ctx.getDeviceProcessor().processDeviceFromEdge(edge.getTenantId(), edge, deviceUpdateMsg));
}
}
if (uplinkMsg.getDeviceCredentialsUpdateMsgCount() > 0) {
for (DeviceCredentialsUpdateMsg deviceCredentialsUpdateMsg : uplinkMsg.getDeviceCredentialsUpdateMsgList()) {
- result.add(ctx.getDeviceProcessor().onDeviceCredentialsUpdate(edge.getTenantId(), deviceCredentialsUpdateMsg));
+ result.add(ctx.getDeviceProcessor().processDeviceCredentialsFromEdge(edge.getTenantId(), deviceCredentialsUpdateMsg));
}
}
if (uplinkMsg.getAlarmUpdateMsgCount() > 0) {
for (AlarmUpdateMsg alarmUpdateMsg : uplinkMsg.getAlarmUpdateMsgList()) {
- result.add(ctx.getAlarmProcessor().onAlarmUpdate(edge.getTenantId(), alarmUpdateMsg));
+ result.add(ctx.getAlarmProcessor().processAlarmFromEdge(edge.getTenantId(), alarmUpdateMsg));
}
}
if (uplinkMsg.getRelationUpdateMsgCount() > 0) {
for (RelationUpdateMsg relationUpdateMsg : uplinkMsg.getRelationUpdateMsgList()) {
- result.add(ctx.getRelationProcessor().onRelationUpdate(edge.getTenantId(), relationUpdateMsg));
+ result.add(ctx.getRelationProcessor().processRelationFromEdge(edge.getTenantId(), relationUpdateMsg));
}
}
if (uplinkMsg.getRuleChainMetadataRequestMsgCount() > 0) {
for (RuleChainMetadataRequestMsg ruleChainMetadataRequestMsg : uplinkMsg.getRuleChainMetadataRequestMsgList()) {
- result.add(ctx.getSyncEdgeService().processRuleChainMetadataRequestMsg(edge.getTenantId(), edge, ruleChainMetadataRequestMsg));
+ result.add(ctx.getEdgeRequestsService().processRuleChainMetadataRequestMsg(edge.getTenantId(), edge, ruleChainMetadataRequestMsg));
}
}
if (uplinkMsg.getAttributesRequestMsgCount() > 0) {
for (AttributesRequestMsg attributesRequestMsg : uplinkMsg.getAttributesRequestMsgList()) {
- result.add(ctx.getSyncEdgeService().processAttributesRequestMsg(edge.getTenantId(), edge, attributesRequestMsg));
+ result.add(ctx.getEdgeRequestsService().processAttributesRequestMsg(edge.getTenantId(), edge, attributesRequestMsg));
}
}
if (uplinkMsg.getRelationRequestMsgCount() > 0) {
for (RelationRequestMsg relationRequestMsg : uplinkMsg.getRelationRequestMsgList()) {
- result.add(ctx.getSyncEdgeService().processRelationRequestMsg(edge.getTenantId(), edge, relationRequestMsg));
+ result.add(ctx.getEdgeRequestsService().processRelationRequestMsg(edge.getTenantId(), edge, relationRequestMsg));
}
}
if (uplinkMsg.getUserCredentialsRequestMsgCount() > 0) {
for (UserCredentialsRequestMsg userCredentialsRequestMsg : uplinkMsg.getUserCredentialsRequestMsgList()) {
- result.add(ctx.getSyncEdgeService().processUserCredentialsRequestMsg(edge.getTenantId(), edge, userCredentialsRequestMsg));
+ result.add(ctx.getEdgeRequestsService().processUserCredentialsRequestMsg(edge.getTenantId(), edge, userCredentialsRequestMsg));
}
}
if (uplinkMsg.getDeviceCredentialsRequestMsgCount() > 0) {
for (DeviceCredentialsRequestMsg deviceCredentialsRequestMsg : uplinkMsg.getDeviceCredentialsRequestMsgList()) {
- result.add(ctx.getSyncEdgeService().processDeviceCredentialsRequestMsg(edge.getTenantId(), edge, deviceCredentialsRequestMsg));
+ result.add(ctx.getEdgeRequestsService().processDeviceCredentialsRequestMsg(edge.getTenantId(), edge, deviceCredentialsRequestMsg));
}
}
if (uplinkMsg.getDeviceRpcCallMsgCount() > 0) {
for (DeviceRpcCallMsg deviceRpcCallMsg : uplinkMsg.getDeviceRpcCallMsgList()) {
- result.add(ctx.getDeviceProcessor().processDeviceRpcCallResponseMsg(edge.getTenantId(), deviceRpcCallMsg));
+ result.add(ctx.getDeviceProcessor().processDeviceRpcCallResponseFromEdge(edge.getTenantId(), deviceRpcCallMsg));
+ }
+ }
+ if (uplinkMsg.getDeviceProfileDevicesRequestMsgCount() > 0) {
+ for (DeviceProfileDevicesRequestMsg deviceProfileDevicesRequestMsg : uplinkMsg.getDeviceProfileDevicesRequestMsgList()) {
+ result.add(ctx.getEdgeRequestsService().processDeviceProfileDevicesRequestMsg(edge.getTenantId(), edge, deviceProfileDevicesRequestMsg));
+ }
+ }
+ if (uplinkMsg.getWidgetBundleTypesRequestMsgCount() > 0) {
+ for (WidgetBundleTypesRequestMsg widgetBundleTypesRequestMsg : uplinkMsg.getWidgetBundleTypesRequestMsgList()) {
+ result.add(ctx.getEdgeRequestsService().processWidgetBundleTypesRequestMsg(edge.getTenantId(), edge, widgetBundleTypesRequestMsg));
+ }
+ }
+ if (uplinkMsg.getEntityViewsRequestMsgCount() > 0) {
+ for (EntityViewsRequestMsg entityViewRequestMsg : uplinkMsg.getEntityViewsRequestMsgList()) {
+ result.add(ctx.getEdgeRequestsService().processEntityViewsRequestMsg(edge.getTenantId(), edge, entityViewRequestMsg));
}
}
} catch (Exception e) {
@@ -1026,7 +694,7 @@ public final class EdgeGrpcSession implements Closeable {
.setCloudType("CE");
if (edge.getCustomerId() != null) {
builder.setCustomerIdMSB(edge.getCustomerId().getId().getMostSignificantBits())
- .setCustomerIdLSB(edge.getCustomerId().getId().getLeastSignificantBits());
+ .setCustomerIdLSB(edge.getCustomerId().getId().getLeastSignificantBits());
}
return builder
.build();
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeRpcService.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeRpcService.java
index ddea009849..fdd2adadd7 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeRpcService.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeRpcService.java
@@ -17,12 +17,15 @@ package org.thingsboard.server.service.edge.rpc;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.EdgeId;
+import org.thingsboard.server.common.data.id.TenantId;
public interface EdgeRpcService {
- void updateEdge(Edge edge);
+ void updateEdge(TenantId tenantId, Edge edge);
- void deleteEdge(EdgeId edgeId);
+ void deleteEdge(TenantId tenantId, EdgeId edgeId);
- void onEdgeEvent(EdgeId edgeId);
+ void onEdgeEvent(TenantId tenantId, EdgeId edgeId);
+
+ void startSyncProcess(TenantId tenantId, EdgeId edgeId);
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSessionState.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSessionState.java
new file mode 100644
index 0000000000..986b271b41
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSessionState.java
@@ -0,0 +1,32 @@
+/**
+ * Copyright © 2016-2021 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.edge.rpc;
+
+import com.google.common.util.concurrent.SettableFuture;
+import lombok.Data;
+import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.ScheduledFuture;
+
+@Data
+public class EdgeSessionState {
+
+ private final Map pendingMsgsMap = new LinkedHashMap<>();
+ private SettableFuture sendDownlinkMsgsFuture;
+ private ScheduledFuture> scheduledSendDownlinkTask;
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java
new file mode 100644
index 0000000000..cb7916965f
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java
@@ -0,0 +1,74 @@
+/**
+ * Copyright © 2016-2021 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.edge.rpc;
+
+import org.thingsboard.server.common.data.edge.Edge;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.service.edge.EdgeContextComponent;
+import org.thingsboard.server.service.edge.rpc.fetch.AdminSettingsEdgeEventFetcher;
+import org.thingsboard.server.service.edge.rpc.fetch.AssetsEdgeEventFetcher;
+import org.thingsboard.server.service.edge.rpc.fetch.CustomerEdgeEventFetcher;
+import org.thingsboard.server.service.edge.rpc.fetch.CustomerUsersEdgeEventFetcher;
+import org.thingsboard.server.service.edge.rpc.fetch.DashboardsEdgeEventFetcher;
+import org.thingsboard.server.service.edge.rpc.fetch.DeviceProfilesEdgeEventFetcher;
+import org.thingsboard.server.service.edge.rpc.fetch.EdgeEventFetcher;
+import org.thingsboard.server.service.edge.rpc.fetch.RuleChainsEdgeEventFetcher;
+import org.thingsboard.server.service.edge.rpc.fetch.SystemWidgetsBundlesEdgeEventFetcher;
+import org.thingsboard.server.service.edge.rpc.fetch.TenantAdminUsersEdgeEventFetcher;
+import org.thingsboard.server.service.edge.rpc.fetch.TenantWidgetsBundlesEdgeEventFetcher;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+public class EdgeSyncCursor {
+
+ List fetchers = new LinkedList<>();
+
+ int currentIdx = 0;
+
+ public EdgeSyncCursor(EdgeContextComponent ctx, Edge edge) {
+ fetchers.add(new SystemWidgetsBundlesEdgeEventFetcher(ctx.getWidgetsBundleService()));
+ fetchers.add(new TenantWidgetsBundlesEdgeEventFetcher(ctx.getWidgetsBundleService()));
+ fetchers.add(new DeviceProfilesEdgeEventFetcher(ctx.getDeviceProfileService()));
+ fetchers.add(new RuleChainsEdgeEventFetcher(ctx.getRuleChainService()));
+ fetchers.add(new TenantAdminUsersEdgeEventFetcher(ctx.getUserService()));
+ if (edge.getCustomerId() != null && !EntityId.NULL_UUID.equals(edge.getCustomerId().getId())) {
+ fetchers.add(new CustomerEdgeEventFetcher());
+ fetchers.add(new CustomerUsersEdgeEventFetcher(ctx.getUserService(), edge.getCustomerId()));
+ }
+ fetchers.add(new AdminSettingsEdgeEventFetcher(ctx.getAdminSettingsService()));
+ fetchers.add(new AssetsEdgeEventFetcher(ctx.getAssetService()));
+ fetchers.add(new DashboardsEdgeEventFetcher(ctx.getDashboardService()));
+ }
+
+ public boolean hasNext() {
+ return fetchers.size() > currentIdx;
+ }
+
+ public EdgeEventFetcher getNext() {
+ if (!hasNext()) {
+ throw new NoSuchElementException();
+ }
+ EdgeEventFetcher edgeEventFetcher = fetchers.get(currentIdx);
+ currentIdx++;
+ return edgeEventFetcher;
+ }
+
+ public int getCurrentIdx() {
+ return currentIdx;
+ }
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AdminSettingsMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AdminSettingsMsgConstructor.java
index 891b42f815..b05fe7f4c2 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AdminSettingsMsgConstructor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AdminSettingsMsgConstructor.java
@@ -18,7 +18,7 @@ package org.thingsboard.server.service.edge.rpc.constructor;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.common.util.JacksonUtil;
-import org.thingsboard.server.gen.edge.AdminSettingsUpdateMsg;
+import org.thingsboard.server.gen.edge.v1.AdminSettingsUpdateMsg;
import org.thingsboard.server.queue.util.TbCoreComponent;
@Component
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AlarmMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AlarmMsgConstructor.java
index 9d471b76ef..c1fc9f0585 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AlarmMsgConstructor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AlarmMsgConstructor.java
@@ -26,8 +26,8 @@ import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.common.util.JacksonUtil;
-import org.thingsboard.server.gen.edge.AlarmUpdateMsg;
-import org.thingsboard.server.gen.edge.UpdateMsgType;
+import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg;
+import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent;
@Component
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AssetMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AssetMsgConstructor.java
index f78956b4f1..a835660dae 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AssetMsgConstructor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AssetMsgConstructor.java
@@ -20,8 +20,8 @@ import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.common.util.JacksonUtil;
-import org.thingsboard.server.gen.edge.AssetUpdateMsg;
-import org.thingsboard.server.gen.edge.UpdateMsgType;
+import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg;
+import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent;
@Component
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/CustomerMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/CustomerMsgConstructor.java
index 5c0ec9ff4b..c5161a674b 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/CustomerMsgConstructor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/CustomerMsgConstructor.java
@@ -19,8 +19,8 @@ import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.common.util.JacksonUtil;
-import org.thingsboard.server.gen.edge.CustomerUpdateMsg;
-import org.thingsboard.server.gen.edge.UpdateMsgType;
+import org.thingsboard.server.gen.edge.v1.CustomerUpdateMsg;
+import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent;
@Component
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DashboardMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DashboardMsgConstructor.java
index a550d52395..f3f8573aa2 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DashboardMsgConstructor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DashboardMsgConstructor.java
@@ -20,8 +20,8 @@ import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.common.util.JacksonUtil;
-import org.thingsboard.server.gen.edge.DashboardUpdateMsg;
-import org.thingsboard.server.gen.edge.UpdateMsgType;
+import org.thingsboard.server.gen.edge.v1.DashboardUpdateMsg;
+import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent;
@Component
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceMsgConstructor.java
index df7bfc9e1a..a28e80d80a 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceMsgConstructor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceMsgConstructor.java
@@ -23,11 +23,11 @@ import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.security.DeviceCredentials;
-import org.thingsboard.server.gen.edge.DeviceCredentialsUpdateMsg;
-import org.thingsboard.server.gen.edge.DeviceRpcCallMsg;
-import org.thingsboard.server.gen.edge.DeviceUpdateMsg;
-import org.thingsboard.server.gen.edge.RpcRequestMsg;
-import org.thingsboard.server.gen.edge.UpdateMsgType;
+import org.thingsboard.server.gen.edge.v1.DeviceCredentialsUpdateMsg;
+import org.thingsboard.server.gen.edge.v1.DeviceRpcCallMsg;
+import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg;
+import org.thingsboard.server.gen.edge.v1.RpcRequestMsg;
+import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent;
import java.util.UUID;
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceProfileMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceProfileMsgConstructor.java
index c3f5346170..a4ab0167a2 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceProfileMsgConstructor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceProfileMsgConstructor.java
@@ -21,8 +21,8 @@ import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
-import org.thingsboard.server.gen.edge.DeviceProfileUpdateMsg;
-import org.thingsboard.server.gen.edge.UpdateMsgType;
+import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg;
+import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent;
@Component
@@ -41,7 +41,7 @@ public class DeviceProfileMsgConstructor {
.setDefault(deviceProfile.isDefault())
.setType(deviceProfile.getType().name())
.setProfileDataBytes(ByteString.copyFrom(dataDecodingEncodingService.encode(deviceProfile.getProfileData())));
- // TODO: voba - should this be always null at the moment??
+ // TODO: @voba - add possibility to setup edge rule chain as device profile default
// if (deviceProfile.getDefaultRuleChainId() != null) {
// builder.setDefaultRuleChainIdMSB(deviceProfile.getDefaultRuleChainId().getId().getMostSignificantBits())
// .setDefaultRuleChainIdLSB(deviceProfile.getDefaultRuleChainId().getId().getLeastSignificantBits());
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EntityDataMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EntityDataMsgConstructor.java
index 1546572531..e4f79bbdf5 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EntityDataMsgConstructor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EntityDataMsgConstructor.java
@@ -25,8 +25,8 @@ import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.transport.adaptor.JsonConverter;
-import org.thingsboard.server.gen.edge.AttributeDeleteMsg;
-import org.thingsboard.server.gen.edge.EntityDataProto;
+import org.thingsboard.server.gen.edge.v1.AttributeDeleteMsg;
+import org.thingsboard.server.gen.edge.v1.EntityDataProto;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.util.TbCoreComponent;
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EntityViewMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EntityViewMsgConstructor.java
index 0f1e3d9e49..6b6447e1e7 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EntityViewMsgConstructor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EntityViewMsgConstructor.java
@@ -20,9 +20,9 @@ import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.common.util.JacksonUtil;
-import org.thingsboard.server.gen.edge.EdgeEntityType;
-import org.thingsboard.server.gen.edge.EntityViewUpdateMsg;
-import org.thingsboard.server.gen.edge.UpdateMsgType;
+import org.thingsboard.server.gen.edge.v1.EdgeEntityType;
+import org.thingsboard.server.gen.edge.v1.EntityViewUpdateMsg;
+import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent;
@Component
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/RelationMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/RelationMsgConstructor.java
index c805085b6b..b4763e3f78 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/RelationMsgConstructor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/RelationMsgConstructor.java
@@ -18,8 +18,8 @@ package org.thingsboard.server.service.edge.rpc.constructor;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.common.util.JacksonUtil;
-import org.thingsboard.server.gen.edge.RelationUpdateMsg;
-import org.thingsboard.server.gen.edge.UpdateMsgType;
+import org.thingsboard.server.gen.edge.v1.RelationUpdateMsg;
+import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent;
@Component
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/RuleChainMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/RuleChainMsgConstructor.java
index c1bd3d816b..8438057a73 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/RuleChainMsgConstructor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/RuleChainMsgConstructor.java
@@ -26,12 +26,12 @@ import org.thingsboard.server.common.data.rule.RuleChainConnectionInfo;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.common.util.JacksonUtil;
-import org.thingsboard.server.gen.edge.NodeConnectionInfoProto;
-import org.thingsboard.server.gen.edge.RuleChainConnectionInfoProto;
-import org.thingsboard.server.gen.edge.RuleChainMetadataUpdateMsg;
-import org.thingsboard.server.gen.edge.RuleChainUpdateMsg;
-import org.thingsboard.server.gen.edge.RuleNodeProto;
-import org.thingsboard.server.gen.edge.UpdateMsgType;
+import org.thingsboard.server.gen.edge.v1.NodeConnectionInfoProto;
+import org.thingsboard.server.gen.edge.v1.RuleChainConnectionInfoProto;
+import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg;
+import org.thingsboard.server.gen.edge.v1.RuleChainUpdateMsg;
+import org.thingsboard.server.gen.edge.v1.RuleNodeProto;
+import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent;
import java.util.ArrayList;
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/UserMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/UserMsgConstructor.java
index f69ede9210..af422cc8ab 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/UserMsgConstructor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/UserMsgConstructor.java
@@ -21,9 +21,9 @@ import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.common.util.JacksonUtil;
-import org.thingsboard.server.gen.edge.UpdateMsgType;
-import org.thingsboard.server.gen.edge.UserCredentialsUpdateMsg;
-import org.thingsboard.server.gen.edge.UserUpdateMsg;
+import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
+import org.thingsboard.server.gen.edge.v1.UserCredentialsUpdateMsg;
+import org.thingsboard.server.gen.edge.v1.UserUpdateMsg;
import org.thingsboard.server.queue.util.TbCoreComponent;
@Component
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetTypeMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetTypeMsgConstructor.java
index 672beeda75..6d8799f8fe 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetTypeMsgConstructor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetTypeMsgConstructor.java
@@ -20,8 +20,8 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.WidgetTypeId;
import org.thingsboard.server.common.data.widget.WidgetType;
import org.thingsboard.common.util.JacksonUtil;
-import org.thingsboard.server.gen.edge.UpdateMsgType;
-import org.thingsboard.server.gen.edge.WidgetTypeUpdateMsg;
+import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
+import org.thingsboard.server.gen.edge.v1.WidgetTypeUpdateMsg;
import org.thingsboard.server.queue.util.TbCoreComponent;
@Component
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetsBundleMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetsBundleMsgConstructor.java
index fba21ed1ca..7412fffcae 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetsBundleMsgConstructor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetsBundleMsgConstructor.java
@@ -20,8 +20,8 @@ import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.WidgetsBundleId;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
-import org.thingsboard.server.gen.edge.UpdateMsgType;
-import org.thingsboard.server.gen.edge.WidgetsBundleUpdateMsg;
+import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
+import org.thingsboard.server.gen.edge.v1.WidgetsBundleUpdateMsg;
import org.thingsboard.server.queue.util.TbCoreComponent;
import java.nio.charset.StandardCharsets;
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java
new file mode 100644
index 0000000000..0035fd1334
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java
@@ -0,0 +1,137 @@
+/**
+ * Copyright © 2016-2021 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.edge.rpc.fetch;
+
+import com.datastax.oss.driver.api.core.uuid.Uuids;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.text.WordUtils;
+import org.springframework.core.io.DefaultResourceLoader;
+import org.thingsboard.server.common.data.AdminSettings;
+import org.thingsboard.server.common.data.edge.Edge;
+import org.thingsboard.server.common.data.edge.EdgeEvent;
+import org.thingsboard.server.common.data.edge.EdgeEventActionType;
+import org.thingsboard.server.common.data.edge.EdgeEventType;
+import org.thingsboard.server.common.data.id.AdminSettingsId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.page.PageData;
+import org.thingsboard.server.common.data.page.PageLink;
+import org.thingsboard.server.dao.settings.AdminSettingsService;
+import org.thingsboard.server.service.edge.rpc.EdgeEventUtils;
+
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@AllArgsConstructor
+@Slf4j
+public class AdminSettingsEdgeEventFetcher implements EdgeEventFetcher {
+
+ private static final ObjectMapper mapper = new ObjectMapper();
+
+ private final AdminSettingsService adminSettingsService;
+
+ @Override
+ public PageLink getPageLink(int pageSize) {
+ return null;
+ }
+
+ @Override
+ public PageData fetchEdgeEvents(TenantId tenantId, Edge edge, PageLink pageLink) throws Exception {
+ List result = new ArrayList<>();
+
+ AdminSettings systemMailSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "mail");
+ result.add(EdgeEventUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ADMIN_SETTINGS,
+ EdgeEventActionType.UPDATED, null, mapper.valueToTree(systemMailSettings)));
+
+ AdminSettings tenantMailSettings = convertToTenantAdminSettings(systemMailSettings.getKey(), (ObjectNode) systemMailSettings.getJsonValue());
+ result.add(EdgeEventUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ADMIN_SETTINGS,
+ EdgeEventActionType.UPDATED, null, mapper.valueToTree(tenantMailSettings)));
+
+ AdminSettings systemMailTemplates = loadMailTemplates();
+ result.add(EdgeEventUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ADMIN_SETTINGS,
+ EdgeEventActionType.UPDATED, null, mapper.valueToTree(systemMailTemplates)));
+
+ AdminSettings tenantMailTemplates = convertToTenantAdminSettings(systemMailTemplates.getKey(), (ObjectNode) systemMailTemplates.getJsonValue());
+ result.add(EdgeEventUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ADMIN_SETTINGS,
+ EdgeEventActionType.UPDATED, null, mapper.valueToTree(tenantMailTemplates)));
+
+ // @voba - returns PageData object to be in sync with other fetchers
+ return new PageData<>(result, 1, result.size(), false);
+ }
+
+ private AdminSettings loadMailTemplates() throws Exception {
+ Map mailTemplates = new HashMap<>();
+ Pattern startPattern = Pattern.compile("");
+ Pattern endPattern = Pattern.compile("